hdoj1074 Doing Homework (状态压缩 DP)

来源:互联网 发布:未来软件家园 编辑:程序博客网 时间:2024/05/30 04:51

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1074

题意:

老师们布置了n(1<=n<=15)门课程的作业,每门作业老师规定了各自的完成期限为xi天(记为deadline),而学生实际完成一门需要yi天(记为finish);各科作业如果学生晚交t天会被该科老师扣t分平时表现成绩,学生如何安排作业才能使平时分扣得最少?各科课程名按字典序输入,每科后面跟着deadline和finish时间,要求输出最少扣分,以及各科作业安排顺序,有多种情况就只输出字典序最小的情况。

思路:

        先讨论最多有n (n=15)门课程的情况,第一反应是这n门课程需要做全排列,有n!种情况,时间复杂度太高行不通;试着考虑先组合,再利用动态规划来优化排列。

        用二进制000000000000000~111111111111111这2^n种状态status表示这n门课程取舍的组合状态(0<=status<2^n),status的二进制位为1表示已经完成该位置的作业,为0表示未完成。

        dp[status]=min{dp[status^(1<<j)]+max(last[status^(1<<j)]+finish[j]-deadline[j], 0), dp[status]},这里0<=j<n,status^(1<<j)表示把status这个状态下的第j个作业标记为未完成,也就是最后完成j作业的前状态,max(last[status^(1<<j)]+finish[j]-deadline[j], 0)表示最后完成j作业贡献的分数,last[status^(1<<j)]表示前状态组合完成作业所花的总时间,这里0<=j<n是在枚举同一status组合状态下所有可以被安排在最后完成作业的情况,这些情况中dp[status]值最小的保留,并记录该状态下最后完成的作业j值。

        当dp[status]有多种情况值相等时,保留字典序较大的j值,因为字典序较大的是最后完成的,满足题意字典序较小放前面;又由于题目中已经说明按字典序输入课程名,因此这里当出现dp[status]等值时,在后面被枚举的状态一定是字典序较大的,可以直接存j值。

#include <stdio.h>#include <string.h>int ans[1<<15];char sub[15][101];int max(int a,int b){return a>b?a:b;}int solve(int s){if(s==0)return 0;solve(s ^ (1<<ans[s]));printf("%s\n",sub[ans[s]]);}int main(){int i,j,n;int cas,deadline[15],finish[15];int dp[1<<15],last[1<<15];int status;int MAXN=1000000000;scanf("%d",&cas);while(cas--){scanf("%d",&n);status=1<<n;for(i=0;i<n;i++)scanf("%s %d %d",sub[i],&deadline[i],&finish[i]);for(i=0;i<status;i++){last[i]=0;for(j=0;j<n;j++){if(i & (1<<j)){last[i]+=finish[j];}}}dp[0]=0;for(i=1;i<status;i++){dp[i]=MAXN;for(j=0;j<n;j++){if(i & (1<<j)){if(dp[i^(1<<j)] + max(0, last[i^(1<<j)] + finish[j] - deadline[j]) <= dp[i]){dp[i]=dp[i^(1<<j)] + max(0, last[i^(1<<j)] + finish[j] - deadline[j]);ans[i]=j;}}}}printf("%d\n",dp[status-1]);solve(status-1);}return 0;}