题意:
给定n<=15个任务,每个任务给定名称(按照字典序排列),给定这个任务的截止时间跟花费的时间,如果过了截止时间还未完成,则过一分钟惩罚1,问惩罚的最小值。
分析:
因为n比较小,并且是让你安排一个排列,所以这就是经典的状压DP。
用S表示状态,二进制1代表做了这个任务,0表示没有做。
从而可以得出状态转移方程(如果当前状态 i 中包含了 j 这个子任务)
因为如果当前状态 i 是最优的值的话,那么他的子状态肯定也是最优的,从而可以由子状态更新当前状态。
那么问题来了,如何计算 呢?
想一下我们可以很清楚的知道,cost一定等于状态所画的总时间T,加上 j 任务所画的时间 f 减去 j 任务截止时间 d
如果是负数的化,那么cost一定等于0了,因为不需要任何惩罚。
所以:
现在又有一个新的问题:T 如何更新。当然这就更简单了,肯定是子状态所用的时间加上当前状态所花费的时间
所以:
从而边大功告成。
要输出路径,只要在更新的时候记录即可。因为是字典序输出,所以枚举子状态时倒叙即可。
// LA4850
#include<bits/stdc++.h>
using namespace std;
const int maxn=1<<16;
int t[maxn];
int dp[maxn];
char str[20][maxn];
int d[maxn];
int f[maxn];
int pre[maxn];
const int inf=0x3f3f3f3f;
void print(int x){
if(!x) return ;
print(x^(1<<pre[x]));
printf("%s\n",str[pre[x]]);
}
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s%d%d",&str[i],&d[i],&f[i]);
}
int sum=1<<n;
for(int i=1;i<sum;i++){
dp[i]=inf;
for(int j=n-1;j>=0;j--){ //字典序最小输出。
if(!(i&(1<<j))) continue;
int tmp=1<<j;
int cost=max(f[j]-d[j]+t[i-tmp],0);
if(dp[i]>dp[i-tmp]+cost){
dp[i]=dp[i-tmp]+cost;
t[i]=t[i-tmp]+f[j];
pre[i]=j;
}
}
}
printf("%d\n",dp[sum-1]);
print(sum-1);
}
return 0;
}