题意:约翰有N头奶牛,组成了一直队伍参加全能比赛。比赛一共有N项,每头奶牛必须参加一项比赛,每项比赛也必须有一头奶牛参加。任何一头奶牛可以胜任任何一项比赛,但得分不一样。如果第i头奶牛参加第j项比赛,在比赛结束的时候,可以为团体总分增加Si,j。 比赛是按照顺序依次进行的。除了上述获得分数的方法之外,还有B种奖励分。获得奖励的方法是在前几项比赛里获得足够的分数。具体来说,第i项奖励会在第Ki项比赛结束的时候检查,如果 当时的总分大于或等于Pi,奶牛们就可以立即获得额外的Ai 分。如果有多项奖励在同一时刻检查,奶牛可以自由安排检查和加分的顺序。请问约翰应该如何安排奶牛参加比赛,才能让它们获得最高的分数?(1≤N,B≤20,1≤Ki≤N ,1≤Pi≤40000,1≤Ai≤1000,1≤Si,j≤1000)
分析:从下午做到晚上,状压的思路很明显,转移方程也很好写.设(f[i])表示当前已经安排好了的(参加了比赛)的奶牛集合状态为(i)时的最高分数.
(f[i]=max(f[i],f[i)$(1$<<$(j-1))]+S[j][calc(i)]+sum[calc(i)][f[i$((1)<<((j-1))]+S[j][calc(i)]])).
解释一下这个麻烦的式子,当前选择用第j头牛参加比赛,(calc(i))表示i的二进制数中为1的个数(即当前第j头牛参加的是第几项比赛),(sum[i][j])表示第i场比赛结束分数为j时能获得多少的额外奖励.
为了不超时,(calc(i),sum[i][j])都必须预处理.调了好久才搞好.时间复杂度是(O(n2^n)).
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
int x=0,o=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')o=-1,ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*o;
}
const int N=21;
const int M=1050000;
int s[N][N],b[M],bj[N],f[M],rev[M],tot[21],sum[N][40005];
vector<pair<int,int> >g[N];
inline int calc(int x){
int cnt=0;
while(x){++cnt;x^=(x&-x);}
return cnt;
}
int main(){
int n=read(),m=read();
for(int i=1;i<=m;++i){
int tim=read(),goal=read(),val=read();bj[tim]=1;tot[tim]+=val;
g[tim].push_back(pair<int,int>(goal,val));
}
for(int i=1;i<=n;++i)//按照获得额外奖励的要求值从小到大排序
if(g[i].size())sort(g[i].begin(),g[i].end());
for(int i=1;i<=n;++i){//预处理sum数组
if(!bj[i])continue;
for(int j=g[i][0].first;j<=40000;++j){//题目说了要求值最大为40000
int cnt=0,x=j;
for(int k=0;k<(int)g[i].size();++k){
if(x>=g[i][k].first)cnt+=g[i][k].second,x+=g[i][k].second;
else break;//这个要求值不能满足,之后的更不会满足
}
sum[i][j]=cnt;
if(cnt==tot[i]){//简单优化一下,现在的分数j就已经能获得全部额外奖励的话,之后的j也全都能
for(int k=j+1;k<=40000;++k)sum[i][k]=cnt;
break;
}
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)s[i][j]=read();
for(int i=1;i<(1<<n);++i)b[i]=calc(i);//预处理每个i中有多少1
int st=1;rev[1]=0;for(int i=1;i<=19;++i)st<<=1,rev[st]=i;//建立2^i与i的映射关系
for(int i=1;i<(1<<n);++i){
int ii=i;
while(ii){
int x=ii&-ii;//找到该状态下所有为1的位
f[i]=max(f[i],f[i^x]+s[rev[x]+1][b[i]]+sum[b[i]][min(f[i^x]+s[rev[x]+1][b[i]],40000)]);
ii^=x;
}
}
printf("%d
",f[(1<<n)-1]);
return 0;
}