题意: T组测试数据。你有n个作业要做,每个作业有一个截止日期D和做完这个作业需要的天数C。一旦你开始做某个作业你就必须把它做完,不能同时做多个作业。每个作业延期完成1天就会产生1的不快乐值,问如何安排做作业顺序使得这个不快乐值最小,如果有多解,输出字典序最小的解。题目中为了方便,给作业的时候是按照字典序从小到大给出的。
思路: dp[state]dp[state] 表示作业完成状态为 statestate 的时候的最优解,cost[state]cost[state]表示作业状态完成状态为 statestate 的时候的花的总天数。statestate 是一个二进制数,0≤state<2n0≤state<2n。
statestate 状态可以由 prepre 状态转移过来,当且仅当二进制下 prepre 是 statestate 某个1变为0的得到的。
假设 prepre 是 statestate 第 ii 个1变为0的得到的,那么 状态转移到 ii 状态的花费
那么状态转移方程为: dp[state]=min(dp[state],dp[i]+temp),(0≤i<n)dp[state]=min(dp[state],dp[i]+temp),(0≤i<n)
状态转移的同时要维护cost[state]cost[state],并记录路径(每一个状态是由哪来的)。
最终 dp[2n−1]dp[2n−1] 就是答案,然后在递归打印路径即可。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20;
const int INF = 0x3f3f3f3f;
char name[MAXN][105];
int n, c[MAXN], d[MAXN], dp[1<<20], cost[1<<20];
struct Path
{
int pre, id;
}path[1<<20];
void putout(int x)
{
if (!x) return ;
putout(path[x].pre);
printf("%s\n", name[path[x].id]);
}
int main()
{
int T; scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%s%d%d", name[i], &d[i], &c[i]);
memset(dp, INF, sizeof(dp));
memset(cost, 0, sizeof(cost));
dp[0] = 0;
for (int state = 1; state < (1<<n); state++)
{
for (int i = 0; i < n; i++)
{
if (state&(1<<i))
{
int pre = state^(1<<i);
int temp = 0;
if (cost[pre]+c[i]-d[i] > 0)
temp = cost[pre]+c[i]-d[i];
if (dp[state] >= dp[pre]+temp)
{
dp[state] = dp[pre]+temp;
cost[state] = cost[pre]+c[i];
path[state].pre = pre;
path[state].id = i;
}
}
}
}
printf("%d\n", dp[(1<<n)-1]);
putout((1<<n)-1);
}
return 0;
}
/*
2
3
Computer 3 3
English 20 1
Math 3 2
3
Computer 3 3
English 6 3
Math 6 3
*/