hdu1074(状态压缩入门)
题目描述:
Doing Homework
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 17631 Accepted Submission(s): 8616
Problem Description
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.
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
题目大意:给我们一些作业的deadlin和cost,让我们安排做作业的顺序,使得处罚最小
思路:这道题和之前在洛谷上做过的一道题有点像,传送门,这道题也是给我们一个deadline和一个cost,求能处理最多的事件个数,
那题的思路是先把所有事件按照最低起始时间排序,从左向右遍历,用大根堆维护,如果出现一个节点的花费时间比
之前已经处理过的节点中的最大花费要小,就不处理(丢弃)那个最大的,而处理这个小的,最后得出最多能处理事件个数就是结果
是不是我们这道题也能那样做呢?显然是不可以的,两道题虽然很相似,但是有一个致命的点就是本题必须要处理所有的作业,尽管超时了,也要补上,即花费的时间
而那道题它不能处理就直接丢弃了,花费是不用计算的,所以,这道题只能用暴力枚举的方法...懂了吧
然而枚举也分好坏,常规枚举就是把所有事件处理顺序的全排列都计算一遍,找出最优解,复杂度O(n!),而如果使用压缩状态dp,这题的复杂度为O((2^n)*n),
复杂度在n大于10的时候小太多,至于如何dp呢,我们考虑一个集合s,表示正在处理的状态,把所有点在s中的状态用0/1表示,0:还未选,1:已经选择了,于是就有了
dp[s][i],表示在所有点在s状态下终点为i的最小花费,然后我们只需要遍历在存在s中(也访问过的)的另一个点j,就能得到转移方程,以下的l:(s-(1<<j));
dp[s][i]=min(dp[s][i],dp[l][j]+t),t就是dp[l][j]状态下到达dp[s][i]状态需要的花费,然后我们再引入一个tim[][]数组,tim[s][i]表示在该状态最小花费(惩罚)下所需的总时间
t=tim[l][j]+cost[i]>=ddl[i]?tim[l][j]+cost[i]-ddl[i]:0;就是如果i的截至日期大于从j状态所需总时间+i事件所需的事件,否则为0,
dp转移的条件就是t+dp[l][j]<dp[s][i],这样就能求出正确解了
能求出正确解的代码:
#include<iostream> #include<string.h> #include<cmath> #include<set> #include<map> #include<string> #include<queue> #include<stack> #include<vector> #include<bitset> #include<algorithm> using namespace std; typedef long long ll; #define inf 0x3f3f3f3f inline int read() { int sum = 0, f = 1; char p = getchar(); for (; !isdigit(p); p = getchar()) if (p == '-')f = -1; for (; isdigit(p); p = getchar()) sum = sum * 10 + p - 48; return sum * f; } const int maxn = 20; int n; int tim[1 << 15][maxn], dp[1 << 15][maxn], predo[1 << 15][maxn]; int ddl[maxn], cost[maxn]; string name[maxn]; void print(int now, int id) { if (now == 0)return; int p = predo[now][id]; print(now - (1 << id), p); printf("%s\n", name[id].c_str()); } void init() { n = read(); for (int i = 0; i < n; i++) { cin >> name[i]; scanf("%d%d", &ddl[i], &cost[i]); } } void solve() { memset(tim, 0x3f3f, sizeof(tim)); memset(dp, 0x3f3f, sizeof(dp)); memset(predo, 0, sizeof(predo)); for (int i = 0; i < n; i++) { dp[1 << i][i] = 0; tim[1 << i][i] = cost[i];//只完成i事件的cost[i] } for (int s = 1; s <= (1 << n) - 1; s++) { for (int i = 0; i < n; i++) { if (!(s & (1 << i)))continue;//s状态i都没完成,推啥推 int l = s - (1 << i); for (int j = n - 1; j >= 0; j--) { //从大到小,因为同花费下要字典序最小,而s集合中的数又已经确定,终点是i,所以你肯定希望倒数第二个终点字典序越大越好 if (!(l & (1 << j)))continue;//同上 int t = ddl[i] >= tim[l][j] + cost[i] ? 0 : tim[l][j] + cost[i] - ddl[i]; if (t + dp[l][j] < dp[s][i]) { predo[s][i] = j; dp[s][i] = t + dp[l][j]; tim[s][i] = tim[l][j] + cost[i]; } } } } int ans = inf, resid = 0; for (int i = n - 1; i >= 0; i--) {//同上也是从大到小 if (dp[(1 << n) - 1][i] < ans) { ans = dp[(1 << n) - 1][i]; resid = i; } } printf("%d\n", ans); print((1 << n) - 1, resid); } int main() { //freopen("test.txt", "r", stdin); int t = read(); while (t--) { init(); solve(); } return 0; }
但是:还是不够,上面的代码A不了,这题咱再好好想想,我们需要管上一个终点是谁嘛,我们只需要当前最优解和当前终点,
当前终点我们已经用predo标记起来了啊,上一个状态的终点我们也已经用predo标记起来了,所以我们当前状态的最优解不需要再去循环
一遍找上一个完成点,只需考虑当前状态最优即可,然后打印的时候用predo打印就可以了....你学废了嘛
AC代码:
#include<iostream> #include<string.h> #include<cmath> #include<set> #include<map> #include<string> #include<queue> #include<stack> #include<vector> #include<bitset> #include<algorithm> using namespace std; typedef long long ll; #define inf 0x3f3f3f3f inline int read() { int sum = 0, f = 1; char p = getchar(); for (; !isdigit(p); p = getchar()) if (p == '-')f = -1; for (; isdigit(p); p = getchar()) sum = sum * 10 + p - 48; return sum * f; } const int maxn = 20; int n; int tim[1 << 15], dp[1 << 15],predo[1<<15]; int ddl[maxn], cost[maxn]; string name[maxn]; void print(int now,int id) { if (now == 0)return; int l = now - (1 << id); int p = predo[l]; print(now-(1<<id) ,p); printf("%s\n", name[id].c_str()); } void init() { n = read(); for (int i = 0; i < n; i++) { cin >> name[i]; scanf("%d%d", &ddl[i], &cost[i]); } } void solve() { memset(tim, 0x3f3f, sizeof(tim)); memset(dp, 0x3f3f, sizeof(dp)); memset(predo, 0, sizeof(predo)); for (int i = 0; i < n; i++) { tim[0]= dp[0] = 0; } for (int s = 1; s<= (1 <<n) - 1; s++) { for (int i = n - 1; i >= 0; i--) { if (!(s & (1 << i)))continue; int l = s - (1 << i); int t = tim[l] + cost[i] >= ddl[i] ? tim[l] + cost[i] - ddl[i] : 0; if (t + dp[l] < dp[s]) { dp[s] = t + dp[l]; tim[s] = tim[l] + cost[i]; predo[s] = i; } } } printf("%d\n",dp[(1<<n)-1]); print((1<<n)-1,predo[(1<<n)-1]); } int main() { //freopen("test.txt", "r", stdin); int t = read(); while (t--) { init(); solve(); } return 0; }