前面虽然自己已经了几篇状压DP的题解,但是这道题才是我的第一道状压DP题。
题意:有n个任务,每个任务有两个属性(d :最后期限 c :需要用的天数),主人公必须一项一项完成任务,并且每个任务完成的时间每超出最后期限一天,就要被扣一分。问他能被扣的最少的分数。多情况字典序输出方案。
设dp[S] 为完成状态S中的所有任务的被扣最少分数。
那么dp[S] = min(dp[S],dp[S-{v}] + cost);
cost 为 完成S中的所有任务所用的总时间 - 完成v的最后期限。
因为输出按照字典序,所以枚举v的时候逆序。这个稍微一想应该就明白。
我的代码:
#include<cstdio>
#include<iostream>
#include<stack>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 16;
struct Nod{
char name[105];
int d,c;
}node[maxn];
int dp[1<<maxn],pre[1<<maxn];
int n;
void solve(){
int Ed = 1 << n;
fill(dp,dp+Ed,inf);
fill(pre,pre+Ed,-1);
int day,cost,S0,tmp,cnt;
dp[0] = 0;
for(int S = 1;S < Ed; S++){
for(int v = n - 1; v >= 0 ; v--){
if(!(S >> v & 1)) continue;
S0 = S - (1 << v);
day = 0;cost = 0;
for(int i = 0; i < n ; i++){
if(S0 >> i & 1) day += node[i].c;
}
if(node[v].d < day + node[v].c) cost = day + node[v].c - node[v].d;
if(dp[S] > dp[S0] + cost){
dp[S] = dp[S0] + cost;
pre[S] = S0;
//cout<<S<<" "<<S0<<" "<<v<<" "<<dp[S]<<endl;
}
}
}
printf("%d\n",dp[Ed-1]);
tmp = Ed - 1;
stack<int> stk;
while(pre[tmp] != -1){
cnt = 0;
while(!((tmp ^ pre[tmp]) >> cnt & 1)) cnt++;
stk.push(cnt);
tmp = pre[tmp];
}
while(!stk.empty()){
int top = stk.top();stk.pop();
printf("%s\n",node[top].name);
}
}
int main(){
int cas;
scanf("%d",&cas);
while(cas--){
scanf("%d",&n);
for(int i = 0 ;i < n; i++){
scanf("%s%d%d",node[i].name,&node[i].d,&node[i].c);
}
solve();
}
return 0;
}