HDU-1074-Doing Homework
Ignatius has just come back school from the 30th ACM/ICPC. Now he has a lot of homework to do. Every teacher gives him a deadline of handing in the homework. If Ignatius hands in the homework after the deadline, the teacher will reduce his score of the final test, 1 day for 1 point. And as you know, doing homework always takes a long time. So Ignatius wants you to help him to arrange the order of doing homework to minimize the reduced score.
Input
The input contains several test cases. The first line of the input is a single integer T which is the number of test cases. T test cases follow.
Each test case start with a positive integer N(1<=N<=15) which indicate the number of homework. Then N lines follow. Each line contains a string S(the subject's name, each string will at most has 100 characters) and two integers D(the deadline of the subject), C(how many days will it take Ignatius to finish this subject's homework).
Note: All the subject names are given in the alphabet increasing order. So you may process the problem much easier.
Output
For each test case, you should output the smallest total reduced score, then give out the order of the subjects, one subject in a line. If there are more than one orders, you should output the alphabet smallest one.
Sample Input
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3
Sample Output
2
Computer
Math
English
3
Computer
English
Math
Hint
In the second test case, both Computer->English->Math and Computer->Math->English leads to reduce 3 points, but the
word "English" appears earlier than the word "Math", so we choose the first order. That is so-called alphabet order.
-----------------------------------------------------------------------------一点也不华丽的分界线-----------------------------------------------------------------------------
这道题是一道典型的状态压缩型dp。
拿到这道题我们首先想的是,怎么解能够把这个题目给解出来。可能因为看刘汝佳的《算法竞赛入门经典入门指南》的时候有道题目影响了我,让我觉得这道题对deadline排下序就可以轻松的解决了。这也是我们的直觉。这样做的话就是一道贪心问题了。但是我们思考一个问题,两个work完成顺序的先后对结果有没有影响?那必然是有的,因为还有一个输出顺序的问题(也许不考虑输出顺序的时候就能够直接排序解决博主未证明这个算法)。例如Computer 20 1,English 5 3, Math 5 1, 输出结果必然是CEM才对,但是一旦排序,那么结果就变成了EMC。既然两个work顺序对结果有影响,那么我们就不能使用贪心来解决这个问题。那么要怎么解决这个问题呢?
我们思考,n个课程,按照deadline排序的结果必然不对,按照handle time排序肯定更加不正确了。那么一定有一个顺序能够解决这个问题,但是n个课程,有n!个顺序,而n!是最大为15!很明显,这种算法可以直接忽略,O(n!)的算法基本上已经可以是最差的算法了,在有更优解法的时候,一定不能采取这个算法。这个时候想到,无论什么顺序我只需要得到n个课程都完成的时候的结果,那么n个课程都完成的状态的上一个状态是什么呢?就是有一门课程没有完成时候的状态,再上一个状态呢?我们发现,按照这个思路,n个课程只有完成和未完成两种状态而且n<=15那么可以直接使用二进制的01表示完成未完成。那么对于第i个课程来说,最优解的状态来自于 i - (1 << j)的状态,j表示第j门课程。这样就得出来一个状态转移方程dp[i] = dp[i - (1 << j)] + cost;得到这个方程之后,就可以非常轻松的撸出来这道题了。当然,由于这道题需要按照字典序输出解决课程的顺序,所以我们需要用一个数组来表示第i个状态的前一个状态。当然可能会有读者问,这样子为什么可以输出字典序的结果?因为输入保证了输入课程是按照字典序来输出,而我们选取课程时是按照逆序来选取,也就是说,如果有两门课程顺序颠倒不影响最小cost,那么字典序靠后的一定会被先记录下来,然后后输出。
代码如下:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define INF 0x3f3f3f3f const int maxn = 17; typedef struct node{ char name[105]; int d, c; }HW; HW hw[maxn]; int dp[1 << 17], sum[1 << 17], pre[1 << 17]; bool cmp(HW a, HW b){ return strcmp(a.name, b.name) >= 0; } inline int Min(int x, int y){ return ( x < y ? x : y ); } inline int Max(int x, int y){ return ( x > y ? x : y ); } void Print(int st){ if(st == 0) return; Print(pre[st]); int j = 0, tmp = st ^ pre[st]; while(tmp){ tmp >>= 1; ++j; } printf("%s\n", hw[j - 1].name); } int main() { // freopen("test.in", "r+", stdin); // freopen("test.out", "w+", stdout); int n, t; scanf("%d", &t); while(t--){ scanf("%d", &n); memset(hw, 0, sizeof(hw)); memset(pre, 0, sizeof(pre)); memset(sum, 0, sizeof(sum)); for(int i = 0; i < n; ++i){ scanf(" %s%d%d", hw[i].name, &hw[i].d, &hw[i].c); } sort(hw, hw + n, cmp); int st = (1 << n) - 1; for(int i = 1; i <= st; ++i) dp[i] = INF; dp[0] = 0; for(int i = 1; i <= st; ++i){ int k; for(int j = 0; j < n; ++j){ int tmp = 1 << j; if(i & tmp){ int tt = sum[i - tmp] + hw[j].c; if(dp[i] > dp[i - tmp] + Max(tt - hw[j].d, 0)){ dp[i] = dp[i - tmp] + Max(tt - hw[j].d, 0); sum[i] = tt; k = i - tmp; } } } pre[i] = k; } printf("%d\n", dp[st]); Print(st); } return 0; }
如有哪里疏漏,请各位大牛指正