题目大意:
根据完成任务的截止时间,超时一天罚1分,求完成所有任务后的最小罚时
这里n最大为15,可以利用状态压缩来解决问题
1 /* 2 首先要明白的一点是状态1/0分别表示这件事做了还是没做 3 而1/0的位置表示这是哪一件事 4 比如说 5 可以表示为101,那么表示第一个和第三个任务已经完成 5 而dp[5]表示第一个和第三个任务完成所花费的最短时间 6 而状态5(101)是从状态1(001)或者状态4(100)转移过来的 7 也就是说我们总是可以通过一个较小的状态不断递推一个较大状态的dp值 8 9 dp[j] = min(dp[i] , dp[j]) j=i|(1<<k) (0<=k<n) 10 也就是说我从 i 这个状态上又完成了第 k 件任务后达到了状态 j 11 12 另外我们要记录任务的顺序,我利用的是rec[]数组,rec[i]表示 13 在第i个状态时最后完成的任务的序号,假设n=5,然后我就可以从 14 11111这个状态开始不断往前记录一个个任务的序号 15 16 */ 17 #include <cstdio> 18 #include <cstring> 19 #include <iostream> 20 21 using namespace std; 22 const int MAXN = 1<<15; 23 //_time[i]记录状态i时完成任务的总时间 24 int id[MAXN+10] , dp[MAXN+10] , _time[MAXN+10] , rec[MAXN+10] , rev[20]; 25 char str[20][105]; 26 int D[20] , C[20]; 27 28 int main() 29 { 30 // freopen("a.in" , "r" , stdin); 31 int T; 32 scanf("%d" , &T); 33 while(T--) 34 { 35 int n; 36 scanf("%d" , &n); 37 for(int i=0; i<n ; i++){ 38 scanf("%s%d%d" , str[i] , D+i , C+i); 39 } 40 int all = 1<<n; //状态总数 41 memset(dp , 0x3f , sizeof(dp)); 42 //0状态表示什么都没做,自然扣除的分数为0 43 dp[0] = 0; 44 memset(_time , 0 , sizeof(_time)); 45 //从小状态不断往前可以不断更新更大状态的值 46 for(int i=0 ; i<all ; i++){ 47 //有n个任务,一个个检测哪个任务可以接在状态i的后面去做 48 for(int j=0 ; j<n ; j++){ 49 int s = 1<<j; 50 if(!(i&s)){ 51 int state = i|s , add; 52 _time[state] = _time[i] + C[j]; 53 add = _time[state]-D[j]>0?_time[state]-D[j]:0;//判断这个新任务加进来是否要被罚时 54 //dp[state] = min(dp[state] , dp[i]+add); 55 //更新rec[],后面一个条件是因为要按字典序排列,所以碰到值相等的情况,往往取较小的那个 56 if(dp[state] > dp[i]+add || (dp[state] == dp[i]+add && strcmp(str[j] , str[rec[state]])>=0)){ 57 dp[state] = dp[i]+add; 58 rec[state] = j; 59 } 60 } 61 } 62 } 63 64 printf("%d\n" , dp[all-1]); 65 66 int st = all - 1; 67 //逆序的,所以要用rev[]重新记录反过来的顺序 68 for(int i=0 ; i<n ; i++){ 69 int index = rec[st] ; 70 rev[n-i-1] = index; 71 st -= (1<<index); 72 } 73 //依次输出 74 for(int i=0 ; i<n ; i++) 75 printf("%s\n" , str[rev[i]]); 76 } 77 return 0; 78 }