1. 程式人生 > >hdu 1074 Doing Homework(狀壓dp)

hdu 1074 Doing Homework(狀壓dp)

對於n種家庭作業,全部做完有n!種做的順序

但是n!太大了,且對於完成作業1,2,3和1,3,2和2,1,3和2,3,1和3,2,1和3,1,2來說

完成它們消耗的天數一定是一樣的,只是完成的順序不同從而score不同

所以可以將 已經完成了某些作業的狀態 用二進位制位壓縮成一種狀態 並記錄score即可

即:狀態壓縮dp

對於到達狀態i,從何種狀態到達i呢?只需要列舉所有的作業

假如對於作業k,i中含有作業k已完成,那麼i可以由和i狀態相同的狀態僅僅是k未完成的

狀態j=i-(1<<k)來完成k到達,並且j一定比i小,如果狀態從0列舉到2^n-1那麼j一定是在i之前已經計算過的

#include <cstdio>
#include <iostream>
using namespace std;
#define INF 0X3f3f3f3f
struct node{
	char name[105];//科目
	int deadline,len;//截止時間,考試時長
}subject[200];
int dp[1<<15+10],times[1<<15+10],pre[1<<15+10];//dp[i]為完成i狀態(壓縮狀態)的最小分數 times[i]為完成i狀態的最小時間點 pre[i]表示當前狀態的上一個完成科目
void printPath(int loc){//遞迴列印路徑
	if(!loc){
		return;
	}
	printPath(loc-(1<<pre[loc]));
	printf("%s\n",subject[pre[loc]].name);
}
int main(){
	int n,t,upper;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		upper=1<<n;//狀態上限
		for(int i=0;i<n;i++){
			scanf("%s%d%d",subject[i].name,&subject[i].deadline,&subject[i].len);
		}
		for(int i=1;i<upper;i++){//從小到大遍歷所有狀態
			dp[i]=INF;
			for(int j=n-1;j>=0;j--){//列舉每一位是否能作為當前狀態的上一個完成科目
				//輸入時按字串由小到大輸入,而每次完成j相當於把j放在後面完成,題目又要求字典序小的那個,
                                //且下面判斷是dp[i]>dp[i-te]+score,所以從n-1開始,
                                //如果下面判斷是dp[i]>=dp[i-te]+score,則從0開始
				int te=1<<j;
				if(!(i&te)){
					continue;
				}
				int score=times[i-te]+subject[j].len-subject[j].deadline;
				if(score<0) score=0;//截止時間前完成得0分
				if(dp[i]>dp[i-te]+score){
					dp[i]=dp[i-te]+score;
					times[i]=times[i-te]+subject[j].len;
					pre[i]=j;
				}
			}
		}
		printf("%d\n",dp[upper-1]);
		printPath(upper-1);
	}
	return 0;
}