状压dp入门题

题目大意:有n人,m个景点,每个景点有一个花费,每个人对每个景点有一个喜爱值,若去某个景点则每个人的bonus为对该景点的喜爱值减去该景点的花费,若两个人同时到某个景点则总bonus加上一个额外值,两两到同一点的额外值通过一个n*n的矩阵表示,每个人可以在中途离开,一旦离开不得再回来,现在旅行路线已经确定,求怎样计划每个人的去留使得总的bonus最大,输出最大bonus,若最大bonus小于等于0,则输出STAY HOME

题目分析:状态压缩dp,dp[i][s]表示经过前i个城市时,状态为s的总bonus的值,状态s表示n个人在或不在的状态,比如dp[3][9]表示经过前三个城市时第一和第四个人还在的状态(9 == 1001),c[i][s]表示经过第i个城市,状态为s的bonus值,先预处理出来c数组,状态转移相当于是通过人数转移(必然是从人多的状态转移到人少的状态,因为离开就不能再回来)所以这里有两种写法(第二种比较快),详细见程序注释 

#include <bits/stdc++.h>
using namespace std;
int const INF = 0x3fffffff;
int n, m;
int dp[11][(1 << 11)], c[11][(1 << 11)];
//dp[i][s]表示经过前i个城市时,状态为s的总bonus的值
//c[i][s]表示经过第i个城市,状态为s的bonus值
int v[11][11], b[11][11], p[11];
void cal()
{
    for(int i = 1; i <= m; i++) //景点数在外边;
    {
        for(int s = 0; s < (1 << n); s++) //2^n种状态
        {
            c[i][s] = 0;    //首先初始化为0
            for(int j = 0; j < n; j++)
            {
                //经过前i个景点第j个人在的状态
                if(s & (1 << j))//二进制表示用这个人
                {
                    //值=喜爱程度-花费
                    c[i][s] += v[j][i] - p[i];
                    //若在第j个人在的状态下,其前的第k个人也在
                    //则说明他们必然同时去了前i个景点,加上额外值
                    for(int k = 0; k < j; k++)
                        if(s & (1 << k))
                            c[i][s] += b[k][j];
                }
            }
        }
    }
}
int main()
{
    while(scanf("%d %d", &n, &m) != EOF && (n + m))
    {
        for(int i = 1; i <= m; i++)
            scanf("%d", &p[i]);     //到每个景点的花费
        for(int i = 0; i < n; i++)
            for(int j = 1; j <= m; j++)
                scanf("%d", &v[i][j]);  //每个人到对每个景点的喜爱程度
        for(int i = 0; i < n; i++)
            for(int j = 0; j < n; j++)
                scanf("%d", &b[i][j]);  //同一个地点多个人去的喜爱度额外值
        cal();  //计算每个城市每种状态下的值
        //初始化dp
        for(int i = 1; i <= m; i++)
            for(int s = 0; s < (1 << n); s++)
                dp[i][s] = -INF;
        int ans = -INF;
        // 写法1:
        // for(int i = 1; i <= m; i++) //景点
        //     for(int s = 0; s < (1 << n); s++) //当前状态s
        //         for(int j = (1 << n) - 1; j >= s; j--)  //s之后的所有状态,因为人走了就不能回来
        //             if((j & s) == s)    //若当前状态是之前某个状态的子状态
        //                 //相当于背包,经过前i个城市,状态为s的值可能由经过前i-1个城市状态为j
        //                 //(j-> s:有人离开)的值加上经过第i个城市状态为s的值
        //                 dp[i][s] = max(dp[i][s], dp[i - 1][j] + c[i][s]);

        // 写法2:
        for(int i = 1; i <= m; i++)
            for(int s = 0; s < (1 << n); s++)
            {
                for(int j = s; ; j = ((j - 1) & s))//把最靠右的第一个1去掉;
                {
                    if(j == 0)  //向前枚举到0
                    {
                        dp[i][0] = max(dp[i][0], dp[i - 1][s] + c[i][0]);
                        break;
                    }
                    //枚举s的子状态(s -> j有人离开),与写法1正好相反
                    dp[i][j] = max(dp[i][j], dp[i - 1][s] + c[i][j]);

                }
            }
        //求游览m个景点下的最大值
        for(int s = 0; s < (1 << n); s++)
            ans = max(ans, dp[m][s]);
        if(ans <= 0)
            printf("STAY HOME\n");
        else
            printf("%d\n", ans);
    }
}

猜你喜欢

转载自blog.csdn.net/lanshan1111/article/details/82913616