题意
有n门功课需要完成,每一门功课都有时间期限以及你完成所需要的时间,如果完成的时间超出时间期限多少单位,就会被减多少学分,问以怎样的功课完成顺序,会使减掉的学分最少,有多个解时,输出功课名排列最小的一个。
思路
本来怎么看都是贪心
但因为要按照字母顺序输出所以贪心不保证
所以15作业个状态压缩来做
枚举每一个状态,枚举每个状态新增的那个节点,再计算最小并记录前一个状态
贪心能够算出正确的分数,但是不一定能得到正确的字典序。 因为贪心的时候根本没有考虑字典序。
但是如果遇到类似题目只要求最小扣分,那么一定要用贪心,因为这题只有15门课,如果课多枚举状态dp的话肯定不行,而贪心就快得多。因此贪心最好也要知道。
即每次都让deadline早的先完成。
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#define INF 0x7f7f7f7f
using namespace std;
const int N = 16;
const int M = 1 << N;
int n;
char s[ N ][ 105 ];
int d[ N ], c[ N ]; // string,deadline,cost
//每个状态的最小超时,输出用的上一个状态,当前状态走过的时间
int dp[ M ], pre[ M ], t[ M ];
void print ( int k ) {
if ( k == 0 )
return;
print ( pre[ k ] );
k -= pre[ k ]; //当前这一步是哪个置了1
for ( int i = 0; i < n; ++i )
if ( k & 1 << i )
puts ( s[ i ] );
}
int main () {
int T;
scanf ( "%d", &T );
while ( T-- ) {
memset ( dp, INF, sizeof ( dp ) );
scanf ( "%d", &n );
for ( int i = 0; i < n; ++i )
scanf ( "%s%d%d", s[ i ], &d[ i ], &c[ i ] );
dp[ 0 ] = t[ 0 ] = 0;
int m = 1 << n;
//枚举1<<n个状态
for ( int i = 1; i < m; ++i )
// 选中该状态下,一个要完成的作业j
for ( int j = 0; j < n; ++j ) {
if ( !( i & 1 << j ) )
continue;
int k = i - ( 1 << j );
t[ i ] = t[ k ] + c[ j ];
//算多出来的时间
int cost = t[ i ] > d[ j ] ? t[ i ] - d[ j ] : 0;
if ( dp[ i ] >= dp[ k ] + cost ) {
dp[ i ] = dp[ k ] + cost;
pre[ i ] = k; //记录路径
}
}
//最后所有作业完成,全是1
printf ( "%d\n", dp[ m - 1 ] );
print ( m - 1 );
}
return 0;
}