一句话题意:
给你n(n<15)个作业,每个作业都有做完需要的时间和最后截止期限,做完交上作业的时间如果超出了最后期限,那么每超出一天会把成绩降一分,问你怎么安排让扣得分最少.
分析:
照着背包想了半天..然后发现根本没法装,原来是状压DP.
最多15个作业,可以用15个二进制位表示,0表示没做,1表示做了(状态压缩).
那么0~(1>>n)-1就可以表示出这n个作业所有做与不做的组合情况
对于任意一个状态,都有一个达到这个状态的最佳值.拿010110举例即第2,4,5份作业已经做了,那么现在做掉第2,4,5份作业,会有一个最佳值,告诉你最少能用多少时间,最少会被扣多少分.
假如我知道达到现在这个状态最少需要的时间,最少扣的分是多少,那么再做一份作业,也就是把这些0其中一个变成1,就可以推出下一个状态需要的时间,扣得分是多少,不过只通过一个状态推出的数值不一定是最佳选择,那么只要把所有能到达这个状态的前一个状态都过一遍,就能保证现在这个状态是最佳的了.
那么你在使用这个状态之前,能保证这个状态是最佳的吗?
可以的.
对于010110来说,只有少一个1的状态才能到达这个状态,然而对于所有少一个1的状态,即000110,010010, 010100,这些数全都小于010110,也就是在使用010110之前,能到达这个状态之前的三个状态都会被跑一遍,保证到达010110时,这个状态已被更新为最佳.
不光是这一个例子,对于所有的01组合,只要少一个1,那么少一个1的这个数一定排在原来那个数的前边.
那么就可以这样从0跑到(1>>n)-1,dp出到达最终状态(全是1)的最佳值是多少了.
题目还要输出作业顺序,那么就再记录一下是哪个上一个状态跑到这个状态有最优解就可以了,如果上一个状态有多个最优解,那么取字典序最小的那个.
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <climits>
#include <stack>
#include <cstdio>
#include <string>using namespace std;struct assignment
{string name;int dl, cost;
}data[100];
struct dynamic_programming
{int time, score, pre;
}dp[1<<15];int main()
{int n, cases;cin >> cases;while (cases--){cin >> n;memset(dp, 0, sizeof(dp));int len = 1;len <<= n;for (int i = 0; i <= len; i++){dp[i].score = 0x3f3f3f3f;dp[i].time = 0;dp[i].pre = -1;}dp[0].score = 0;getchar();for (int i = 0; i <= n - 1; i++)cin >> data[i].name >> data[i].dl >> data[i].cost;for (int i = 0; i <= len - 1; i++){for (int j = 0; j <= n - 1; j++){if(!(1 << j & i)){int next = i + (1 << j);int score = dp[i].time + data[j].cost - data[j].dl;if(score < 0)score = 0;if(dp[next].score > dp[i].score + score){dp[next].time = dp[i].time + data[j].cost;dp[next].score = dp[i].score + score;dp[next].pre = j;}if(dp[next].score == dp[i].score + score && data[j].name > data[dp[next].pre].name){dp[next].pre = j;}}}}int id = (1 << n) - 1;cout << dp[id].score << endl;stack <string> stk;while (dp[id].pre != -1){stk.push(data[dp[id].pre].name);id -= (1 << dp[id].pre);}while (!stk.empty()){cout << stk.top() << endl;stk.pop();}}return 0;
}