选做题 - 2
马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。
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
问题分析
状压DP
解题思路
转移方程比较简单,每次枚举最后一个完成的作业就行了。
转移方程是 dp[i] = min{ dp[ i - (1<<(k-1)) ] + score(i,k) }
dp[i]表示i的二进制中所有1对应的作业按照顺序写所扣得分
t[]表示i的二进制中所有1所对应的作业按照顺序写所花的总时间(天数)
score(i,k)是完成i这么多作业中最后一个完成的作业是k时,k带来的最小扣分值
代码实现
#include <algorithm>
#include <iostream>
#define inf 0x7fffff
#define maxn 20
#define maxm (1<<15)+5
using namespace std;
int D[maxn],C[maxn],dp[maxm],pre[maxm],t[maxm];
char s[20][110];
void print(int x)
{
if(!x) return ;
print(x-(1<<pre[x]));
cout<<s[pre[x]]<<endl;
}
int main()
{
int n,T;
cin>>T;
while (T--)
{
cin>>n;
for(int i=0;i<n;i++)
cin>>s[i]>>D[i]>>C[i];
for(int i=1;i<(1<<n);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]+C[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]+C[j];
pre[i]=j;
}
}
}
cout<<dp[(1<<n)-1]<<endl;
print((1<<n)-1);
}
return 0;
}