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 3Sample Output
2 Computer Math English 3 Computer English MathHint
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题目最多15种作业,用二进制压缩表示,枚举1-(1<<n)-1每种状态,每次dp更新的时候就想最短路一样,
先从小到大枚举找到这种状态里没有做的那个作业,加上这个作业计算减的分数,如果可以是减得分数变小就更新同时用一个pre数组记录下它的之前状态即新状态有之前那个状态转移而来,最后递归输出,因为题目给出的作业名字是按字典序递增顺序给出的,所以我们从小到大枚举可以保证字典序最小,具体看代码
code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 16;
const int INF = 0x3f3f3f3f;
struct node{
char name[110];
int D,C;
}h[MAXN];
int dp[1<<MAXN];
int pre[1<<MAXN];
int n;
void output(int state){
if(state == 0) return;
int t = 0;
for(int i = 0; i < n; i++){
if((state & (1<<i)) != 0 && (pre[state] & (1<<i)) == 0){//找到那个当前状态有,它的前一个状态没有的作业
t = i;//说明这个作业是这一次做的,并且题目告诉我们给的作业名字按字母增序给出,所以我们从0-n枚举一定能保证字典序最小
break;
}
}
output(pre[state]);
printf("%s\n",h[t].name);//回溯的时候输出,因为是倒着往前找,然后正向输出
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i = 0; i < n; i++){
scanf("%s%d%d",h[i].name,&h[i].D,&h[i].C);
}
for(int i = 0; i < (1<<n); i++){
dp[i] = INF;
}
dp[0] = 0;
for(int i = 0; i < (1<<n); i++){
for(int j = 0; j < n; j++){
if(i & (1<<j)) continue;//如果这个已经做了,跳过
int s = 0;//用于计算减的分数
for(int k = 0; k < n; k++){
if(i & (1<<k))
s += h[k].C;
}
s += h[j].C;//加上新加的状态
if(s > h[j].D) s -= h[j].D;//加上这个状态后需要天数大于deadline,相减便是减的分数
else s = 0;//否则减分为0
if(dp[i|(1<<j)] > dp[i]+s){
dp[i|(1<<j)] = dp[i] + s;//更新状态
pre[i|(1<<j)] = i;//记录下新状态的前一个状态
}
}
}
printf("%d\n",dp[(1<<n)-1]);
output((1<<n)-1);
}
return 0;
}