题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1074
思路:
看着数据很小,15,但是完成的顺序有15!情况,这么大的数据是无法实现的。上网查才知道要用状态压缩,用二进制表示状态,比如n=3时:111表示3門全部完成,011表示完成第1,2門的状态,000表示一門都没完成的情况。这样压缩之后最多就只有1<<15种状态了,然后直接dp就可以了。大体思路是从1状态到(1<<n-)1依次遍历,寻找上一个状态,使得到达此状态时总扣分最小,寻找上一个状态时j从n-1开始到0倒着遍历,这样才能保证最终结果是字典序最小的,这里要仔细想一下,比如寻找111的上一个状态,若j=1和j=0对应的上一个状态同时使111这个状态的总扣分最小,因为输入的顺序即按字典序来的,所以j=1对应的作业字典序要大于j=0的,所以要让j=0对应的作业先于j=1的完成,所以应该选择j=1作为上一个状态,故应该倒着遍历。详见代码。
AC代码如下:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int inf=0x3f3f3f3f; 5 struct course{ 6 char name[105]; 7 int d,c; 8 }a[20]; 9 10 struct node{ //上一个状态,扣分值,总用时,该状态所做的作业编号 11 int pre,redu,cost,cou; 12 }dp[1<<15+5]; 13 14 int T,n,maxn; 15 16 void print(int p){ //递归逆向输出 17 if(p){ 18 print(dp[p].pre); 19 printf("%s\n",a[dp[p].cou].name); 20 } 21 } 22 23 int main(){ 24 scanf("%d",&T); 25 while(T--){ 26 scanf("%d",&n); 27 maxn=1<<n; 28 for(int i=0;i<n;i++) 29 scanf("%s%d%d",a[i].name,&a[i].d,&a[i].c); 30 memset(dp,0,sizeof(dp)); 31 for(int i=1;i<maxn;i++){ 32 dp[i].redu=inf; //找最小的扣分,所以初始化为inf 33 for(int j=n-1;j>=0;j--){ //倒着遍历,为了结果字典序最小 34 int t=1<<j; 35 if(i&t){ 36 int tmp=i-t; //上一个状态 37 int tt=dp[tmp].cost+a[j].c-a[j].d; 38 if(tt<0) tt=0; //扣分值若小于0则置0 39 if(dp[tmp].redu+tt<dp[i].redu){ 40 dp[i].redu=dp[tmp].redu+tt; 41 dp[i].pre=tmp; 42 dp[i].cost=dp[tmp].cost+a[j].c; 43 dp[i].cou=j; 44 } 45 } 46 } 47 } 48 printf("%d\n",dp[maxn-1].redu); 49 print(maxn-1); 50 } 51 return 0; 52 }