hdu1074 doing homework
题目链接:https://vjudge.net/problem/HDU-1074
状压dp。将作业从0-n-1存储。对于二进制m位为1表示做了第m门作业(右一位定义为第0位)。可以写出转移方程:f[i]=min(f[st]+max(time-a[j].d,0)),此处st表示如果状态i的j位为1也就是做了第j门作业时,将第j位的1变为0的状态。也就是表示最后做的作业为j之前所需要花费的代价+做完作业j后产生的代价中取最小值,time表示做j之前其他作业产生的总时间,可以遍历st的二进制位计算得出。这样已经可以求出最小时间即为f[(1<<n)-1]
对于字典序最小方案的打印,一开始认为既然作业名称已经按照字典序排序了,每次回溯的时候总是找到更靠前的状态更好。虽然在vjudge上过了(数据有点水?),但在hdu上发现了hack数据。脑补一下字典序的比较方法,感觉不一定是对的?因为这只能保证回溯的那一个位置取更小的字典序,并不能保证整个的字典序最小。于是干脆把所有解都列举了出来比较字典序(见代码),在vjudge上也过了......
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct hw{ 4 string s; 5 int d,t; 6 }a[20]; 7 const int inf=0x3f3f3f3f; 8 int f[1<<17],c[1<<17]; 9 string s[20],ans[20]; 10 int n,i,j,m,tt,k; 11 12 void dfs(int i,int l){ 13 if (i==0) { 14 if (ans[0]=="") for (int j=0;j<n;j++) ans[j]=s[j]; 15 int f=0; 16 for (int j=0;j<n;j++) 17 if (s[j]!=ans[j]){ 18 if (s[j]<ans[j]) f=1; 19 break; 20 } 21 if (f==1) for (int j=0;j<n;j++) ans[j]=s[j]; 22 } 23 int st; 24 for (int j=n-1;j>=0;j--){ 25 if ((i>>j)&1){ 26 st=i&(~(1<<j)); 27 if (f[st]+max(c[i]-a[j].d,0)==f[i]) { 28 s[l]=a[j].s; 29 dfs(st,l-1); 30 } 31 } 32 } 33 } 34 35 int main(){ 36 //freopen("hdu1074.txt","r",stdin); 37 cin>>tt; 38 while (tt--){ 39 cin>>n; 40 for (i=0;i<n;i++) cin>>a[i].s>>a[i].d>>a[i].t; 41 memset(f,0,sizeof(f)); 42 memset(c,0,sizeof(c)); 43 for (i=0;i<n;i++) ans[i]=s[i]=""; 44 for (i=1;i<=(1<<n)-1;i++){ 45 f[i]=inf; 46 for (j=0;j<n;j++) if ((i>>j)&1) c[i]+=a[j].t; 47 for (j=0;j<n;j++) 48 if ((i>>j)&1){ 49 int st=i&(~(1<<j)); 50 int time=a[j].t; 51 for (k=0;k<n;k++) if ((st>>k)&1) time+=a[k].t; 52 f[i]=min(f[i],f[st]+max(time-a[j].d,0)); 53 } 54 } 55 cout<<f[(1<<n)-1]<<endl; 56 dfs((1<<n)-1,n-1); 57 for (int j=0;j<n;j++) cout<<ans[j]<<endl; 58 } 59 //fclose(stdin); 60 return 0; 61 }