题意
有
n
n
个朋友, 个景点。按顺序进行游览,可以中途退出,也可以不参加。一个人在第
i
i
个景点将花费 ,第
i
i
人在景点 能获得
Vi,j
V
i
,
j
,而第
i
i
个朋友与第 个朋友在一起将获得额外的
Bi,j
B
i
,
j
,求最终结果的最大值。
1≤n,m≤10
1
≤
n
,
m
≤
10
思路
一些朋友两两间额外值的总和比较费时,先预处理。然后用 dpi,j d p i , j 记录第 i i 个景点,有 这些人的最大结果(可以滚动)。接下来,我们面临的问题是如何实现“部分人走掉”这个操作。不难发现,走后的人是走前的人的子集。那么对于一群人 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。
注意到 的变化存在单调性。例如子集的枚举,无论是减运算还是且运算都存在不递增的性质,所以不可能重复。只看减运算,发现循环把比
m
m
小的数都枚举了一遍,而且运算又筛出了所有的子集进入循环体,并排除了很多不可能的情况,保证了复杂度。所以这两行代码的正确性显然。
而枚举一个 个元素的集合,再枚举它的子集,复杂度又如何呢?我们把枚举
n
n
个元素的集合按元素个数分开。含 个元素的集合有
C1n
C
n
1
个,含
2
2
个元素的集合有 个……含有
n
n
个元素的集合有 。再分别枚举子集,总的得到枚举出子集的个数是
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
,由组合数的性质得到原式结果为
3n−1
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;
}