题意:有n个作业,每个作业都有完成所需要的天数以及最迟上交日期,如果迟与上交日期在上交,每天都会被扣一分,问最少要被扣多少分,并输出完成作业的顺序,如果有好几种方案,输出字典序最小的那个。
思路:状态压缩dp,因为一共最多就15种作业,所以可以把每种作业完成与否用0和1表示,0表示没写,1表示写了,则我们可以得到一个二进制数i,代表作业的完成状态,用dp[i]表示当状态到达i时最少扣的分数,dp[i]可由所有dp[1-(1<<j)]推出,j是第j中作业,并且在i表示的状态中,第j种作业已经写了,另外开一个spend数组,spend[i]记录达到状态i一共需要的天数,就是状态i中每种写完的作业所需要的时间的和。最后得到的dp[(1<<n)-1]就是扣的最少的分。那么怎么输出字典序最小的那种情况呢?我们先开一个ans数组,用来存最后的答案,一开始另cur=(1<<n)-1,也就是最后的状态,我们看它是由那一个状态推出来的,我们就吧它存起来,如果有好几个的话,我们就存字典序最大的那个(因为沃我们现在求得顺序和实际答案是相反的,所以是求字典序最大的那个),然后改变cur的值,也就是把cur变成推出cur的那个状态值,再继续下去,直到我们求出n个答案之后,结束循环,把答案倒着输出来就可以了。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int dp[1<<16];
struct subject{
char s[120];
int d,x;
}a[20];
int spend[1<<16];
char ans[20][110];
int main()
{
int cas;
cin>>cas;
while(cas--){
memset(dp,0x3f3f,sizeof(dp));
memset(spend,0,sizeof(spend));
int n;
cin>>n;
for(int i=0;i<n;i++)
scanf("%s %d %d",a[i].s,&a[i].d,&a[i].x);
dp[0]=0;
for(int i=1;i<(1<<n);i++)
for(int j=0;j<n;j++) if(i&(1<<j)){
spend[i]=spend[i-(1<<j)]+a[j].x;
//cout<<spend[i]<<' '<<i<<endl;;
break;
}
//cout<<endl;
for(int i=1;i<(1<<n);i++){
for(int j=0;j<n;j++) if(i&(1<<j)){
int k=i-(1<<j);
if(a[j].x+spend[k]>a[j].d)
dp[i]=min(dp[i],dp[k]+a[j].x+spend[k]-a[j].d);
else dp[i]=min(dp[i],dp[k]);
//cout<<i<<' '<<dp[i]<<endl;
}
}
cout<<dp[(1<<n)-1]<<endl;
int cur=(1<<n)-1;
char ch[120]="aa";
int pos;
for(int i=0;i<n;i++){
bool flag=1;
for(int j=0;j<n;j++) if(cur&(1<<j)){
//cout<<1<<endl;
int k=cur-(1<<j),x;
if(spend[k]+a[j].x>a[j].d)
x=spend[k]+a[j].x-a[j].d;
else x=0;
if(dp[k]+x==dp[cur]){
if(flag){
flag=0;
strcpy(ch,a[j].s);
pos=j;
}
else if(strcmp(ch,a[j].s)<0){
strcpy(ch,a[j].s);
pos=j;
}
}
}
strcpy(ans[i],ch);
cur-=(1<<pos);
}
for(int i=n-1;i>=0;i--)
cout<<ans[i]<<endl;
}
return 0;
}