E-选做题-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
Hint
在第二个样例中,按照 Computer->English->Math 和 Computer->Math->English 的顺序完成作业,
所扣的分数都是 3,由于 English 的字典序比 Math 小,故输出前一种方案。
二、思路与算法
这道题需要利用状压dp。状压dp——>举例说明,一共5个任务,完成第零个和第三个,那么表示为01001,所以通过一个数字可以看出现在所有任务的完成情况。
而检查是否完成过任务j——>按位与1<<j,如果计算结果为1,就是任务j完成过。其他关于任务的计算都类似,依靠1<<j。
核心计算思想为,完成任务j之后扣的最少分数=上一个状态(没有完成任务j)扣的最少分数+max(0,上一个状态结束的时间+任务j耗时-任务j截止日期)。
pre数组是前驱数组,用来记录每个状态目前完成的最后一个任务,这个数组在计算时没有什么作用,主要在于输出的时候从后向前回溯,输出任务完成的顺序,在计算的时候,需要对pre进行记录。
sum数组用来记录每个状态的所有用时,在计算时需要。
f数组用来记录每个状态扣的最少分数。
三、代码实现
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
int m=0,n=0;
struct homework{
string name;
int ddl,cost;
};
homework ho[20];
int pre[1<<16]; //记录当前状态最后一个作业
int f[1<<16]; //记录状态S被扣最少分数
int sum[1<<16]; //记录状态S所有用时
void output(int x){
//通过前驱回溯路线
if(x!=0){
output(x-(1<<pre[x]));
cout<<ho[pre[x]].name<<"\n";
}
return;
}
int main(){
scanf("%d",&m); //组数
for(int i=0;i<m;i++){
scanf("%d",&n); //科目数目
for(int j=0;j<n;j++){
cin>>ho[j].name>>ho[j].ddl>>ho[j].cost;
} //输入完成
//开始枚举
memset(pre,-1,sizeof(pre));
memset(f,0,sizeof(f));
memset(sum,0,sizeof(sum));
for(int j=1;j<(1<<n);j++){
f[j]=0x3f3f3f3f;
for(int k=n-1;k>=0;k--){
if(!(j&(1<<k))){continue; }
int tmp=max(0,sum[j-(1<<k)]+ho[k].cost-ho[k].ddl);
if(f[j]>f[j-(1<<k)]+tmp)
{
f[j]=f[j-(1<<k)]+tmp;
sum[j]=sum[j-(1<<k)]+ho[k].cost;
pre[j]=k;
}
}
}
cout<<f[(1<<n)-1]<<"\n";
output((1<<n)-1);
}
return 0;
}