2015-8-24
问题简述:
有N项任务,每一项有一个名称,一个截止日期,一个完成所需时间。若一项任务的完成时间超过了截止日期一天,则惩罚加一,类推。。。要求按字典序输出能得到最小惩罚的名称序列。
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=1074
解题思路:
如果按照所有任务的全排列遍历一遍取最小值的话,尽管1<=N<=15,但是全排列为N!,也必然会超时。
已知N最大为15,选取任务时状态共有2^15,可以用二进制表示这些状态,1表示当前状态选取了该任务,0表示未选,则可使用动态规划思想:
dp[sta]表示状态为 sta 时的最小惩罚,
dp[sta|(1<<i)] = dp[sta] + max(0, sum + c[i] - d[i]), sum表示当前状态已使用时间总和。
再借助一个 pre 数组存储当前状态的前一个状态,以遍按选取按顺序任务序列。
源代码:
/*
OJ: HDOJ
ID: xj1215615213
TASK: 1074.Doing Homework
LANG: C++
NOTE: 状态压缩
*/
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define MAX (1<<15)+10
#define rep(i,j,k) for(int i=j;i<k;i++)
using namespace std;
int d[16], c[16], n;
char str[16][101];
int dp[MAX], pre[MAX];
void Print(int sta) {
if( sta == 0 )
return;
int u = 0;
rep(i,0,n) {
if( (sta&(1<<i)) != 0 && (pre[sta]&(1<<i)) == 0 ) {
u = i;
break;
}
}
Print( pre[sta] );
printf( "%s\n", str[u] );
}
int main()
{
int t;
scanf( "%d", &t );
while( t -- ) {
scanf( "%d", &n );
rep( i, 0, n )
scanf( "%s %d %d", str[i], &d[i], &c[i] );
memset( dp, 0x3f3f3f3f, sizeof(dp) );
memset( pre, 0, sizeof(pre) );
dp[0] = 0;
rep(sta,0,(1<<n)) {
int sum = 0;
rep(i,0,n)
if( sta&(1<<i) )
sum += c[i];
rep(i,0,n) {
if( (sta&(1<<i)) == 0 ) {
if(dp[sta|(1<<i)] > dp[sta] + max( 0, sum + c[i] - d[i] ) ) {
dp[sta|(1<<i)] = dp[sta] + max( 0, sum + c[i] - d[i] );
pre[sta|(1<<i)] = sta;
}
}
}
}
printf( "%d\n", dp[(1<<n)-1] );
Print( (1<<n)-1 );
}
return 0;
}