题目来源:http://acm.hdu.edu.cn/showproblem.php?pid=1074
★自己立的flag 非要选择学打牌 ,那么就从状压DP开始吧~
题意:
给你n个作业(名字,提交截止日期,需要写的时间),每个作业超过预定时间提交,每多一天就要扣一分。问怎样安排写作业的顺序才能使扣的分最少,并按输出写的作业的顺序~
思路:
首先看到n特别小,然后emmm,就是状压DP咯 其实就是因为在状压专题 。
假设现在有三门作业 那么会有 1<<3 种状态 000 001 010 011 100 101 110 111
我们需要推导从一种状态到另一种状态的关系 比如: 000 到 001 就是选择了第一本书
通过一个循环枚举 状态 (从小到大),然后内层循环枚举 种类 (从大到小,方便输出)
最后的 DP [ 1<<n -1 ] 就是答案
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<stack>
#include<queue>
#include<set>
using namespace std;
const int maxn=1e4+5;
const int sz=1<<20;
const int mod=1e9+7;
typedef long long LL;
int dp[sz],time[sz],op[sz];
int n,m;
struct node
{
char c[105];
int e,len;
}f[20];
inline void read(int &x)
{
char c; x=1;
while((c=getchar())<'0'||c>'9') if(c=='-') x=-1;
int res=c-'0';
while((c=getchar())>='0'&&c<='9') res=res*10+c-'0';
x*=res;
}
void out(int x)
{
if(x==0) return ;
out(x-(1<<(op[x]-1)));
cout<<f[op[x]].c<<endl;
}
int main()
{
int t;read(t);
while(t--){
read(n);
for(int i=1;i<=n;i++) scanf("%s %d%d",f[i].c,&f[i].e,&f[i].len); //输入
for(int i=1;i<=(1<<n)-1;i++){
dp[i]=2e9; //初始化
for(int j=n;j>=1;j--){
int l=1<<(j-1); //l 为只有j的状态
if(l&i){ //如果是可以到达状态 i 的 前一种状态 就继续运行
int score=time[i-l]+f[j].len-f[j].e; //前一个状态的时间+作业的长度-截止日期 即为这个作业扣分
// cout<<time[i-l]<<' '<<j<<endl;
if(score<0) score=0;
if(dp[i]>dp[i-l]+score){
dp[i]=dp[i-l]+score;
time[i]=time[i-l]+f[j].len;
op[i]=j; //用于输出
}
}
}
// for(int j=1;j<=(1<<n);j++)
// cout<<dp[j]<<' ';
// cout<<endl;
}
cout<<dp[(1<<n)-1]<<endl;
out((1<<n)-1);
}
return 0;
}