HDU-4049 Tourism Planning(状压DP)

题意

n n 个朋友, m 个景点。按顺序进行游览,可以中途退出,也可以不参加。一个人在第 i i 个景点将花费 Pi,第 i i 人在景点 j 能获得 Vi,j V i , j ,而第 i i 个朋友与第 j 个朋友在一起将获得额外的 Bi,j B i , j ,求最终结果的最大值。
1n,m10 1 ≤ n , m ≤ 10

思路

一些朋友两两间额外值的总和比较费时,先预处理。然后用 dpi,j d p i , j 记录第 i i 个景点,有 j 这些人的最大结果(可以滚动)。接下来,我们面临的问题是如何实现“部分人走掉”这个操作。不难发现,走后的人是走前的人的子集。那么对于一群人 j j 我们只用枚举它的子集即可。子集,父集的枚举方法一般如下式:

for(int B=m;B;B=(B-1)&m)      //枚举m的非空子集{B},若是全部子集将判断提到循环末。
for(int B=m;B<=M;B=(B+1)|m)   //枚举m的父集{B},上界为M。

注意到 B 的变化存在单调性。例如子集的枚举,无论是减运算还是且运算都存在不递增的性质,所以不可能重复。只看减运算,发现循环把比 m m 小的数都枚举了一遍,而且运算又筛出了所有的子集进入循环体,并排除了很多不可能的情况,保证了复杂度。所以这两行代码的正确性显然。
而枚举一个 n 个元素的集合,再枚举它的子集,复杂度又如何呢?我们把枚举 n n 个元素的集合按元素个数分开。含 1 个元素的集合有 C1n C n 1 个,含 2 2 个元素的集合有 Cn2 个……含有 n n 个元素的集合有 Cnn。再分别枚举子集,总的得到枚举出子集的个数是 C1n21+C2n22+C3n23+...+Cin2i+...Cnn2n C n 1 2 1 + C n 2 2 2 + C n 3 2 3 + . . . + C n i 2 i + . . . C n n 2 n ,由组合数的性质得到原式结果为 3n1 3 n − 1 ,那么复杂度是 3n 3 n
那么此题就可解了。

代码

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define lowbit(x) ((x)&-(x))
typedef long long LL;
using namespace std;
int c[13],v[13][13],b[13][13];
int bonus[1030],bin[1030],dp[2][1030];
int n,m;

int main()
{
    FOR(i,1,10)bin[1<<i]=i;
    while(scanf("%d%d",&n,&m),n||m)
    {
        memset(bonus,0,sizeof(bonus));
        FOR(i,1,m)scanf("%d",&c[i]);
        FOR(i,0,n-1)FOR(j,1,m)scanf("%d",&v[i][j]);
        FOR(i,0,n-1)FOR(j,0,n-1)scanf("%d",&b[i][j]);
        FOR(i,1,(1<<n)-1)
        {
            bonus[i]=bonus[i^lowbit(i)];
            for(int j=i^lowbit(i);j;j^=lowbit(j))
                bonus[i]+=b[bin[lowbit(i)]][bin[lowbit(j)]];
        }
        memset(dp,0x8f,sizeof(dp));
        dp[0][(1<<n)-1]=0;
        FOR(i,0,m-1)
        {
            memset(dp[~i&1],0x8f,sizeof(dp[~i&1]));
            FOR(j,0,(1<<n)-1)
                for(int k=j;;k=(k-1)&j)
                {
                    int sum=bonus[k];
                    for(int t=k;t;t^=lowbit(t))
                        sum+=v[bin[lowbit(t)]][i+1]-c[i+1];
                    dp[~i&1][k]=max(dp[~i&1][k],dp[i&1][j]+sum);
                    if(!k)break;
                }
        }
        int ans=-2e9;
        FOR(i,0,(1<<n)-1)ans=max(ans,dp[m&1][i]);
        if(ans>0)printf("%d\n",ans);
        else printf("STAY HOME\n");
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值