题目:
传送门
思路:
我们不妨先从贪心的角度出发,对于给定的 n个任务,我们肯定一个做完了以后立马再接着做另一个.所以可以得出来做完n个任务时的终止时间是固定的,即所有任务所需要的时间之和.
那我们不妨状态压缩,状态s表示已经有哪些任务已经做了,那么很明显s状态下一定有个最小的扣分.所以dp[i]表示在状态i下的最少扣分,每次我们找到 不在状态i下的另一个任务,让其和状态 i 合并来更新下一个状态。
然后我们现在要保证字典树最小,因为发现最多只有 15 个数,我们不妨用 15 进制将其hash成一个数字,然后我们只要比较数字就可以知道大小了.
AC_Code
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <stack>
using namespace std;
const int maxn = 16;
const int MAXN = (1<<15);
const int inf = 1e8;
struct P
{
int cost,lit;
char name[110];
}s[maxn];
int n;
char name[maxn][110];
long long dp[MAXN];
long long imp[MAXN];
int gettime(int x) {
int sum =0;
for(int i=0;x;i++) {
if(x&1) sum += s[i].cost;
x >>= 1;
}
return sum;
}
int main() {
int t;
scanf("%d",&t);
while(t--) {
scanf("%d",&n);
for(int i=0;i<n;i++) {
scanf("%s%d%d",s[i].name,&s[i].lit,&s[i].cost);
}
int up = (1<<n);
for(int i=0;i<up;i++) {
dp[i] = inf;
imp[i]=0;
}
dp[0] =0;
for(int i=0;i<up;i++) {
int sums = gettime(i);
// cout<<i<<' '<<dp[i]<<' '<<imp[i]<<endl;
for(int j=0;j<n;j++) {
int tmp = (1<<j);
if((i&tmp)) continue;
int news = (i|tmp);
int val = (sums + s[j].cost <= s[j].lit ? 0:sums+s[j].cost-s[j].lit);
if(val + dp[i] < dp[news]) {
dp[news] = dp[i]+val;
imp[news] = imp[i]*15+j;
// cout<<news<<endl;
}
else if(val + dp[i] == dp[news]) {
if(imp[i]*15+j < imp[news]) {
imp[news] = imp[i]*15+j;
}
}
}
}
stack<int> sta;
printf("%d\n",dp[up-1]);
for(int i=0;i<n;i++) {
sta.push(imp[up-1]%15);
imp[up-1] /=15;
}
while(!sta.empty()) {
printf("%s\n",s[sta.top()].name);
sta.pop();
}
}
return 0;
}