之前做过一道二进制压缩的题目,感觉也不是很难吧,但是由于见少识窄,这道题一看就知道是撞鸭dp,却总是无从下手....最后看了一眼博客,才顿悟,本次做这道题的作用知识让自己更多的认识二进制压缩,并无其它卵用......呜呜呜~~~
本题大意:到期末了,某同学的n位老师给他布置了n门家庭作业,要求他在布置作业后的D天之内完成,并且已知它每门作业所需的天数C,每门作业超时一天就要扣一分,让你求出要如何合理安排做作业的顺序才能使他扣掉的总分最少......还需要输出做作业的顺序和最优情况下扣的分数......
本题思路:因为要得到所有做题的顺序就有n!种这么多,那么此时我们看到n <= 15,那就能考虑到二进制撞鸭了,二进制撞鸭之前发过一个博客有讲过,这里再强调一下比如0101表示第1和3科的作业做了,第二科作业没做,那么我们可以枚举做作业先后的所有的情况,然后取最优即可,这只是思路,那么具体我们要如何分析这个问题呢
下面我详细讲解......
首先思考一个问题:我们二进制枚举所有的写作先后过程,那么我们的dp要保存什么状态呢,第一我们需要先找出最小分数即score,需要回溯保存一个过程的上一个过程pre,需要每次写作业之后更新已经过去的时间time,需要保存当前正在写作业的下标ind,就ok了,对于dp[ i ],我们在所有科目中逆序寻找(为什么是逆序寻找呢....因为我们需要利用一个数组存做作业的先后顺序,也就是利用一个pre存它之前完成的状态的编号,遍历输出时顺序也是反的,为了保证字典序输出,我们只能逆着遍历,~~~~(>_<)~~~~呜呜呜~~~),如果发现i状态下如果已经完成了作业j,那么我们就将作业j分离出来在这一步单独再次完成,在这一步利用dp[i - 1<<j]将dp[ i ]的值更新并找到最小值(由于在完成三项任务1, 2, 3时肯定要完成对应的前两项1,2 || 2,3 || 1,3),那么我们的最优值就是由这个思路得来的,由于i - 1<<j 恒小于i,所以它在i之前求得,我们就可以对dp[ i ]的值进行更新,那么对于dp[ i ],我们就可以写出他的状态转移方程dp[ i ] = max(dp(i - 1 << j) + dp[ j ])(j 为状态i 的一个子问题)。
参考代码:
1 #include <iostream> 2 #include <cstring> 3 #include <string> 4 #include <stack> 5 using namespace std; 6 7 const int maxn = 15 + 2, INF = 0x3f3f3f3f; 8 int t, n; 9 struct node { 10 string name; 11 int deadline, cost; 12 } a[maxn]; 13 struct node1 { 14 int pre, time, score, ind; 15 } dp[1 << 15]; 16 17 int main () { 18 ios::sync_with_stdio(false); 19 cin >> t; 20 while(t --) { 21 cin >> n; 22 for(int i = 0; i < n; i ++) 23 cin >> a[i].name >> a[i].deadline >> a[i].cost; 24 memset(dp, 0, sizeof dp); 25 int nbit = 1 << n; 26 for(int i = 1; i < nbit; i ++) { 27 dp[i].score = INF; 28 for(int j = n - 1; j >= 0; j --) { 29 int temp = 1 << j; 30 if(!(temp & i)) continue; 31 int tem = i - temp; 32 int t = dp[tem].time + a[j].cost - a[j].deadline; 33 t = t > 0 ? t : 0; 34 if(t + dp[tem].score < dp[i].score) { 35 dp[i].score = t + dp[tem].score; 36 dp[i].pre = tem; 37 dp[i].ind = j; 38 dp[i].time = dp[tem].time + a[j].cost; 39 } 40 } 41 } 42 cout << dp[nbit - 1].score << endl; 43 stack<int> q; 44 int t = nbit - 1; 45 while(dp[t].time) { 46 q.push(dp[t].ind); 47 t = dp[t].pre; 48 } 49 while(!q.empty()) { 50 int k = q.top(); 51 cout << a[k].name << endl; 52 q.pop(); 53 } 54 } 55 return 0; 56 }