思路:
第一道状压dp,以前下决心学过一次,未果,今年再战! 十分感谢博主黑色的夢。
- 用二进制压缩状态(用0表示没做,1表示做了),当n=3时,dp[111]表示三门课都做了,为我们最终求的状态。
- dp[011]:表示第1、2门课做了,第3门没做(二进制从右至左看)。
- 定义好状态,就要讨论状态转移了,做了三门课的最优解由做了两门课的解得到,做了两门的最优解由做了一门课的解得到,做了一门课的最优解就为其本身了。
- dp[001]:表示只做了第1门课的最优解,dp[010]:只做第2门课的最优解,dp[100]…
- 关于顺序输出处理,对于样例2,手动画一下状态转移图示,就是到程序中为什么是从第n门课到第1门课的顺序判断了。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
const int Max_n=16;
int t,n;
struct node{
int d,c; //结束时间和所需天数
char s[110];
}no[Max_n];
int dp[1<<Max_n],time[1<<Max_n],par[1<<Max_n];
//dp[]表示此状态对应扣的分数,time[]表示完成此状态的时刻 ,par[]记录状态转移路径
void output(int k){ //递归打印路径
if(!k)return;
output(k-(1<<par[k]));
printf("%s\n",no[par[k]].s);
}
int main()
{
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%s%d%d",no[i].s,&no[i].d,&no[i].c);
memset(time,0,sizeof(time));
memset(dp,0x3f,sizeof(dp)); //初始化无穷大
dp[0]=0; //dp[000]表示一门课没做,值为0
int total=1<<n;
for(int i=1;i<total;i++){
for(int j=n-1;j>=0;j--){ //j从大到小判断是否能通过做第j门课达到状态i(例如:状态011可由状态010通过做第1门课得到)
int t=1<<j;
if(!(i&t))continue; //不能通过做第j门课达到
int s=time[i-t]+no[j].c-no[j].d; //状态i-t选做j门课达到状态i所需的代价
if(s<0)s=0; //当然不可能为负,最小为0
if(dp[i]>dp[i-t]+s){ //更新状态最优解
dp[i]=dp[i-t]+s;
time[i]=time[i-t]+no[j].c;
par[i]=j; //记录路径,很巧妙,orz~
}
}
}
printf("%d\n",dp[total-1]);
output(total-1);
}
return 0;
}