题意:
马上假期就要结束了,zjm还有 n 个作业,完成某个作业需要一定的时间,而且每个作业有一个截止时间,若超过截止时间,一天就要扣一分。
zjm想知道如何安排做作业,使得扣的分数最少。
Tips: 如果开始做某个作业,就必须把这个作业做完了,才能做下一个作业。
input:
有多组测试数据。第一行一个整数表示测试数据的组数。
第一行一个整数 n(1<=n<=15)
接下来n行,每行一个字符串(长度不超过100) S 表示任务的名称和两个整数 D 和 C,分别表示任务的截止时间和完成任务需要的天数。
这 n 个任务是按照字符串的字典序从小到大给出。
output:
每组测试数据,输出最少扣的分数,并输出完成作业的方案,如果有多个方案,输出字典序最小的一个。
样例:
输入:
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3
输出:
2
Computer
Math
English
3
Computer
English
Math
思路:
状压DP实在是有点不大能想到,跟着PPT上代码思路和同学们的一些解释写的,能看懂,但自己的话肯定想不到,也不会用。主要让自己熟悉一下这个题型吧。
首先f[S],其中S为任务集合,sum[i],为每个集合的对应的总时间,pre[]为前序,即在S的时候记录加入的是哪一个任务。通过枚举每一个任务集合,即S,然后对这个S集合,枚举每一个任务,看加入当前任务的时候,其总罚时的情况,如果更小,则更新f[S],sum[S]和pre[S]。其主要的转移方程都是课上的PPT上的。最终输出最后的发f[max],即可得到答案。然后根据pre[],依次找到输出的任务即可。有一个可以学习的,就是要求的字典序最小输出,可以在一开始的时候就对各个任务进行排序,之后得到的便是字典序最小的。
代码:
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
const int inf=1e8;
struct work{
int d,c;
string Name;
bool operator<(const work &p)const{
if(Name!=p.Name) return Name<p.Name;
}
}W[20];
int m,n;
int f[1<<16], sum[1<<16],pre[1<<16]; //记录罚时 作业集合对应的总时间
void output(int x){
if(x==0) return;
output(x-(1<<pre[x]));
cout<<W[pre[x]].Name<<endl;
}
int main(){
ios::sync_with_stdio(0);
cin>>m;
for(int i=0; i<m;i++){
cin>>n;
memset(f,0,sizeof(f));
memset(sum,0,sizeof(sum));
for(int j=0;j<n;j++){
cin>>W[j].Name>>W[j].d>>W[j].c;
}
sort(W,W+n);
int now=(1<<n)-1; //记录当前最大的集合
int tmp;
for(int S=1;S<=now; S++){ //枚举每一个状态
f[S]=inf;
for(int x=0;x<n;x++){ //枚举每一个作业
if(!(S&(1<<x))) continue; //没有x的点
tmp=max(sum[S-(1<<x)]+W[x].c-W[x].d,0); //加入这个点的总罚时
if(f[S]>=f[S-(1<<x)]+tmp){
f[S]=f[S-(1<<x)]+tmp;
sum[S]=sum[S-(1<<x)]+W[x].c;
pre[S]=x; //在S时加入了第x任务
}
}
}
cout<<f[now]<<endl;
output(now);
}
return 0;
}