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
大意:
有n门课程作业,每门作业的截止时间为D,需要花费的时间为C,若作业不能按时完成,每超期1天扣1分。
这n门作业按课程的字典序先后输入 ,问完成这n门作业至少要扣多少分,并按字典序从小到大输出扣分最少的做作业顺序。
如何实现DP,观察数量级n<=15,是不是可以枚举所有的情况来判段哪些作业的先后顺序,1<<15,完全可以开一个数组存放
那这样说的话就可以枚举00…… 1到1111…… 1这些作业的情况,当然所有的作业是都需要做完的,用1表示做了的作业,那么1左移 的每一位都代表这每种作业的做与不做的情况。
dp[i]表示状态为i(二进制的情况)最小扣分情况
#include<bits/stdc++.h>
#include<string>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define ms(a,b) memset(a,b,sizeof a)
const int INF=0x3f3f3f3f;
struct node
{
//char project[200];
string project;//课程
int dead;//最后的期限
int need;//需要做完的时间
}num[20];
struct node1
{
int pre;//先前转态,就是从哪一个状态推出当前状态的
int cost;//做完当前状态的最小扣分
int time;//所需要的总时间
int now;//当前所选取的作业
}dp[1<<15];
int n;
int main() {
int t;
ios::sync_with_stdio(0);cin.tie(0);
cin>>t;
while(t--)
{
cin>>n;
//ms(num,0);
ms(dp,0);
rep(i,0,n-1){
cin>>num[i].project;
cin>>num[i].dead;
cin>>num[i].need;
}
int m=1<<n;
for(int i=1;i<m;i++)
{
dp[i].cost=INF;
for(int j=n-1;j>=0;j-)//可以从最后一个状态往前推哪一个作业,比如:i=101,temp=100
{//那么当前i这种作业情况是temp推出来的,也就是做完1和3个作业是在做完3号作业的
int temp=1<<j;//的前提下才有的
if(temp&i)
{
int p=i-temp;//那就看1号作业的情况
int tt=dp[p].time+num[j].need-num[j].dead;//扣分=写完前一种作业的
//时间+写完当前作业需要的时间-要交当前作业的时间
if(tt<=0) tt=0;//如果是负数,就是说能够在交作业之前写完,那样置零,不会扣分
if(tt+dp[p].cost<dp[i].cost)
{
dp[i].cost=tt+dp[p].cost;//最小化扣分
dp[i].pre=p;//记录父节点
dp[i].now=j;//记录是选了哪一门
dp[i].time=dp[p].time+num[j].need;//需要的时间
}
}
}
}
//下面需要自行体会这个按着字典序从小到大输出的方法,你可以这样理解,给出的样例是已经排好的字典序
//我们是从后往前推的dp,因此这样输出的顺序正好是从大到小的顺序,那就需要翻转一下。
cout<<dp[m-1].cost<<endl;
stack<int>ss;
int k=m-1;
while(dp[k].time)
{
//cout<<num[dp[k].now].project<<endl;
ss.push(dp[k].now);
k=dp[k].pre;
}
while(ss.size())
{
int k=ss.top();
cout<<num[k].project<<endl;
ss.pop();
}
}
return 0;
}