题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1074
题意:有n个作业,对于每一个作业有一个deadline,有一个完成这作业所需要的时间。如果超过deadline一天就要扣一分,求怎么安排写作业的顺序才能保证扣的分最少。
思路:
对于每一个作业有三个元素,名字,ddl,所需要的时间。
用不同的状态表示当前作业完成的情况,运用状压dp
定义集合S为已经完成的作业的集合,u为当前集合S中的作业编号。
对于每一个状态也有三个元素,一个为达到这个状态所需要的时间,一个为最少扣的分数,因为还需要记录转移的路径所以需要一个来记录由哪里转移来。
i j 表示不同状态,则i^j表示i 状态剔除j 状态的之外的。
i&j 表示i状态中包含了j的状态。
转移方程:
tmp表示由S-{u}这个状态转移过来,需要多扣的分数。
dp[S].lowscore = min(dp[S].lowscore,dp[S-{u}].lowscore + tmp); (u属于S)
如果更新了扣分,就同时需要记录路径,和更新所需要的时间。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stack>
using namespace std;
#define M 20
#define INF 0x3f3f3f3f
struct
{
int time;
int lowscore;
int pre;
}dp[1<<M];
struct
{
int cost;
int ddl;
char name[109];
}course[M];
int n;
int main()
{
//freopen("input.txt","r",stdin);
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i = 0;i < n;i++)
{
scanf("%s %d %d",course[i].name,&course[i].ddl,&course[i].cost);
}
for(int i = 0;i < 1<<n;i++)
{
dp[i].time = 0;
dp[i].lowscore = INF;
dp[i].pre = -1;
}
dp[0].lowscore = 0;
for(int S = 0;S < 1<<n;S++)
{
for(int u = 0;u < n;u++)
{
if(S & (1<<u))
{
int temp = dp[S^(1<<u)].time + course[u].cost - course[u].ddl;
if(temp < 0) temp = 0;
if(dp[S].lowscore >= dp[S^(1<<u)].lowscore + temp) //加上等于号以在相同状态下保证pre 尽可能的大,就能保证前面的尽可能小
{
dp[S].pre = u;
dp[S].lowscore = dp[S^(1<<u)].lowscore + temp;
dp[S].time = dp[S^(1<<u)].time + course[u].cost;
}
}
}
}
printf("%d\n",dp[(1<<n)-1].lowscore);
stack<int> ss;
for(int i = (1<<n)-1;;)
{
ss.push(dp[i].pre);
i = i ^ (1<<dp[i].pre);
if(i == 0) break;
}
while(!ss.empty())
{
int temp;
temp = ss.top();
ss.pop();
printf("%s\n",course[temp].name);
}
}
return 0;
}