1.题意
马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。
2.样例
Input
有多组测试数据。第一行一个整数表示测试数据的组数
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。
Output
每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。
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
在第二个样例中,按照 Computer->English->Math 和 Computer->Math->English 的顺序完成作业,所扣的分数都是 3,由于 English 的字典序比 Math 小,故输出前一种方案。
3.解题思路
- 对于n种家庭作业,全部做完有n!种做的顺序,太大了,考虑状态压缩,dp[i]记录到达状态i所扣的分数,t[i]记录到达状态i所花的时间
- 对于状态i,从何种状态到达i ?,只需要枚举所有的作业,如对作业k,i中已经有k完成,则i可以由和i相同的状态,仅仅是K未完成的状态j=i-(1<<k)来完成k的到达,且j一定比i 小从状态0枚举到2^n-1,则j一定是在i之前算过的
- 先枚举状态,再倒序枚举j,因为要保证字典序,j从大到小,因为每次完成j相当于把j放在后面完成,它的上一状态则从小到大,则输出的为字典序最小的那个
4.AC代码
#include<stdio.h>
#include<algorithm>
#include<stack>
#include<iomanip>
#include<iostream>
#include<cstring>
using namespace std;
const int M=(1<<15)+5;
const int INF=1e8;
int n;
int dp[M],t[M],pre[M],f[20],d[20];
char s[20][105];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(s,0,sizeof(s));
memset(t,0,sizeof(t));
memset(pre,0,sizeof(pre));
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s%d%d",&s[i],&d[i],&f[i]);
}
int bit=(1<<n)-1;
for(int i=1;i<=bit;i++)
{
dp[i]=INF;
for(int j=n-1;j>=0;j--)
{
int tmp=(1<<j);
if(!(i&tmp)) continue;
int score=t[i-tmp]+f[j]-d[j];
if(score<0) score=0;
if(dp[i]>dp[i-tmp]+score)
{
dp[i]=dp[i-tmp]+score;
t[i]=t[i-tmp]+f[j];
pre[i]=j;
}
}
}
printf("%d\n",dp[bit]);
stack<int>st;
st.push(pre[bit]);
while(bit-(1<<pre[bit])!=0)
{
bit-=(1<<pre[bit]);
st.push(pre[bit]);
}
while(!st.empty())
{
printf("%s\n",s[st.top()]);
st.pop();
}
}
}