题意:给了几门学科作业、它们的截止提交期限(天数)、它们的需要完成的时间(天数),每项作业在截止日期后每拖延一天扣一学分,算最少扣的学分和其完成顺序。
一开始做的时候,只是听说过状态压缩这个神奇的东西,但事实上我并不会用它,所以白白想了一个晚上没想出来,然后就看了一下题解```再见吧朋友又是新的算法要学了。
状态压缩,实际上就是用二进制的方式,对于每一个要考察的状态用0/1表示其完成与否,这样当从 1 遍历到 111```111 的时候,就可以遍历完所有的状态了,在遍历的过程中利用位运算以及状态转移就能够最终实现DP。
首先对于每一个状态 i 初始化其罚分为最大值用于最开始的比较,用 & 运算来判断它的其中某项作业 j 是否已经完成,完成则 & 运算结果为 1 ,否则为 0 ,若已完成,那么考察 j 作业未完成的情况 last ,如果从 last 状态的罚分加上完成作业 j 后的罚分小于当前 i 状态的罚分,则更新 i 状态的罚分情况,并且记录下到 i 状态时做的作业 j 、 i 的上一状态 last 、i 状态的已花费时间,便于进行状态转移和记录路径。最后用栈从全部作业完成时 111`````111 的状态开始通过记录的上一状态往前递推,输出作业次序。
收获颇丰,但是心力交瘁```代码中间绿绿的是调试的时候用的```因为我蠢```
1 #include<stdio.h> 2 #include<string.h> 3 #include<stack> 4 #include<algorithm> 5 #define INF 1<<30 6 using namespace std; 7 struct Sub{ 8 char name[100]; 9 int d,t; 10 }S[16]; 11 12 struct dpl{ 13 int t,s,p,n; 14 }dp[1<<16]; 15 16 int main(){ 17 int T; 18 while(scanf("%d",&T)!=EOF){ 19 int N; 20 for(int q=1;q<=T;q++){ 21 scanf("%d",&N); 22 memset(dp,0,sizeof(dp)); 23 int i,j; 24 for(i=1;i<=N;i++){ 25 scanf("%s%d%d",S[i].name,&S[i].d,&S[i].t); 26 } 27 /* for(i=1;i<=N;i++){ 28 29 30 printf("%s %d %d",S[i].name,S[i].d,S[i].t); 31 }*/ 32 int sum=(1<<N)-1,sub,last,pun; 33 /* printf("%d ",sum); 34 while(sum){ 35 printf("%d",sum&1); 36 sum>>=1; 37 } 38 printf(" ");*/ 39 for(i=1;i<=sum;i++){ 40 dp[i].s=INF; 41 for(j=N;j>0;j--){ 42 sub=1<<(j-1); 43 if(i&sub){ 44 last=i-sub; 45 pun=dp[last].t+S[j].t-S[j].d; 46 if(pun<0)pun=0; 47 if(pun+dp[last].s<dp[i].s){ 48 dp[i].s=pun+dp[last].s; 49 dp[i].t=dp[last].t+S[j].t; 50 dp[i].p=last; 51 dp[i].n=j; 52 } 53 } 54 } 55 } 56 stack<int>s; 57 int t=sum; 58 printf("%d ",dp[t].s); 59 while(t){ 60 s.push(dp[t].n); 61 t=dp[t].p; 62 } 63 while(!s.empty()){ 64 printf("%s ",S[s.top()].name); 65 s.pop(); 66 } 67 } 68 } 69 return 0; 70 }