把最近做的一些狀態壓縮DP整理一下。DP苦手,從簡單的開始。
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=1297
zoj 2297
題目大意是和n人車輪戰,你可以選擇對戰順序,和每個人對戰會消耗ci的體力,若此時體力小於0(注意是小於不是不大於),則失敗,打過一個人後會恢複ri的體力。問你有沒有一種順序使得你能夠打完最後一個人。
很明顯的狀態壓縮DP,開始因為小於0的問題WA一次(不科學呀,誰看過那個遊戲HP減到0還活蹦亂跳的!),設dp[flag]為打過的人的狀態為flag時所剩HP的最大值,則
dp[flag]=max(dp[flag']-c[i]+r[i])其中dp[flag']>=c[i]且flag'=flag^(1<<i),因為最後一個人是固定的,所以我們最後比較dp[flag](這裡flag表示除最後一個人外其讓人都已打過的狀態 )和c[n]即可,代碼如下:
#include <iostream>#include <string.h>#include <algorithm>#include <stdio.h>using namespace std;int dp[1<<20];int c[21],r[21];int max(int a,int b){ return a>b?a:b;}int min(int a,int b){ return a<b?a:b;}int n;int dfs(int flag){ if(dp[flag]!=-1) return dp[flag]; int i; int ans=0; for(i=1;i<n;i++) { if(flag&(1<<(i-1))) { int tmp=dfs(flag^(1<<(i-1))); if(tmp>=c[i]) { ans=max(ans,min(100,tmp-c[i]+r[i])); } } } return dp[flag]=ans;}int main(){ //freopen("dd.txt","r",stdin); while(scanf("%d",&n)!=EOF) { int i; for(i=1;i<n;i++) { scanf("%d%d",&c[i],&r[i]); } scanf("%d",&c[n]); int limit=(1<<(n-1))-1; for(i=0;i<=limit;i++) dp[i]=-1; dp[0]=100; if(dfs(limit)>=c[n]) printf("clear!!!\n"); else printf("try again\n"); } return 0;}
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4345
zoj 3502
也是很基礎的狀態壓縮DP吧,這裡只要清楚期望值就是把每個題目的正確率相加即可。最大期望值很好求。但是我WA了7次,為什麼呢?因為還要把題目的順序求出來,並且還要是萬惡的字典序最小。。。這裡設dp[flag]表示此狀態下過的題的最大期望值(0表示這道題還沒看,1表示這道題已做,不管是否正確)。求所有題都做過後的最大期望值即可,dp[flag]很好求,這裡不再詳述,求具體順序稍微麻煩點,我暴力的,設way[flag]表示此狀態下達到最大期望值時的最小字典序,然後暴力更新,今天這道題算是整死我了。。。。代碼如下:
#include <iostream>#include <string.h>#include <algorithm>#include <stdio.h>using namespace std;int bo[10][10];char way[1<<10][10];int dp[1<<10];int n;int dfs(int flag){ if(dp[flag]!=-1) return dp[flag]; int i,j,k,num=0; int a[10]; for(i=0;i<n;i++) { if(flag&(1<<i)) { a[num++]=i; } } //printf("%d\n",num); int ans=-1,po; for(i=0;i<num;i++) { int tmp=0; int now=a[i]; for(j=0;j<num;j++) { if(bo[a[j]][now]>tmp) { tmp=bo[a[j]][now]; } } if(tmp+dfs(flag^(1<<now))>ans) { ans=tmp+dfs(flag^(1<<now)); strcpy(way[flag],way[flag^(1<<now)]); int len=strlen(way[flag]); way[flag][len]=now+'A'; way[flag][len+1]='\0'; po=now; } else if(tmp+dfs(flag^(1<<now))==ans) { if(strcmp(way[flag^(1<<now)],way[flag^(1<<po)])<0) { strcpy(way[flag],way[flag^(1<<now)]); int len=strlen(way[flag]); way[flag][len]=now+'A'; way[flag][len+1]='\0'; po=now; } } } return dp[flag]=ans;}int aa[10];int main(){ //freopen("dd.txt","r",stdin); int ncase; scanf("%d",&ncase); while(ncase--) { int i,j; scanf("%d",&n); for(i=0;i<n;i++) { for(j=0;j<n;j++) { scanf("%d",&bo[i][j]); } } int limit=(1<<n)-1; memset(dp,-1,sizeof(dp)); memset(way,'\0',sizeof(way)); dp[0]=0; int ans=dfs(limit); printf("%.2f\n",(double)ans/100); printf("%s\n",way[limit]); } return 0;}
http://poj.org/problem?id=2923
poj 2923
題目大意:一個人要搬家,有兩輛車,兩輛車的載重量分別為c1,c2,現在給你每一件傢具的重量c[i],問最少來回幾次可以把這些傢具搬完。
我們設dp[0][flag]表示用第一輛車搬狀態為flag的傢具最少要來會多少次(flag中1表示搬這件傢具,0表示不搬),對於第二輛車我們可以類似處理,設w[0][flag]表示第一輛車搬完了狀態為flag的傢具後,其最大剩餘載重量。那麼狀態轉移就變得非常明顯。我們對於每一個狀態,枚舉本次要搬的傢具,設為i,(前提是目前狀態包含i),則在搬第i件傢具前的狀態為flag'=(flag^(1<<i)),若w[0][flag']>=c[i],則dp[flag]=dp[flag'],否則dp[flag]=dp[flag']+1,我們取dp[flag]的最小值,同時對於最小值相同的情況,我們要保證w[0][flag]最大。求完之後,我們一次枚舉狀態flag計算dp[0][flag]和dp[1][~flag](這裡的~flag指的是flag取反)的較大值,然後去這些值中最小的一個即可。代碼如下:
#include <string.h>#include <algorithm>#include <stdio.h>#define inf 2100000000using namespace std;int dp[2][1<<10],left[2][1<<10],c[2],v[11];int n;int min(int a,int b){ return a<b?a:b;}int max(int a,int b){ return a>b?a:b;}int dfs(int flag,int t){ //printf("f"); if(dp[t][flag]!=-1) return dp[t][flag]; if(flag==0) { left[t][flag]=c[t]; return dp[t][flag]=1; } int i; int ans=inf,ll=0,num,le,tans,tleft; for(i=1;i<=n;i++) { if(flag&(1<<(i-1))) { int tmp=flag^(1<<(i-1)); num=dfs(tmp,t),le=left[t][tmp]; if(le<v[i]) { if(v[i]>c[t]) { left[t][flag]=0; return dp[t][flag]=inf; } else { tleft=c[t]-v[i]; tans=num+1; } } else { tleft=le-v[i]; tans=num; } if(tans<ans) { ans=tans; ll=tleft; } else if(tans==ans&&tleft>ll) { ll=tleft; } } } left[t][flag]=ll; return dp[t][flag]=ans;}int main(){ // freopen("dd.txt","r",stdin); int ncase,time=0; scanf("%d",&ncase); while(ncase--) { printf("Scenario #%d:\n",++time); memset(dp,-1,sizeof(dp)); scanf("%d%d%d",&n,&c[0],&c[1]); for(int i=1;i<=n;i++) scanf("%d",&v[i]); int limit=(1<<n)-1; int ans=inf; for(int i=0;i<=limit;i++) { int tmp=max(dfs(i,0),dfs(limit-i,1)); ans=min(ans,tmp); } printf("%d\n\n",ans); } return 0;}
http://poj.org/problem?id=3229
poj 3229
此題題意飄忽不定,很難把握,我感覺很難在不看discuss的情況下1Y,其實就是建圖後一個類似於TSP問題的題,不過題目加了一點就是在每個節點還有一個停留時間,然後需要注意的是這題從原點出發最後回到一點只要求一次原點的停留時間,還有不是必須要訪問所有點,每個點可以經過無數次,所以我們建完圖後要用floyd預先處理一下,其他就沒什麼好講得了吧,其實題目並不難,關鍵還是題意太模糊,搞得人不太敢去做。這道題類似於poj3311(題解這裡有http://blog.csdn.net/dyx404514/article/details/8758376),很簡單的狀態轉移,這裡就不重複了。代碼如下:
#include <iostream>#include <string.h>#include <algorithm>#include <stdio.h>#define inf 2100000000using namespace std;int n,m,k;double map[16][16];int time[16];double dp[15][1<<14];double min(double a,double b){ return a<b?a:b;}int max(int a,int b){ return a>b?a:b;}void floyd(){ int i,j,k; for(i=0;i<n;i++) { for(j=0;j<n;j++) { for(k=0;k<n;k++) { map[j][k]=min(map[j][i]+map[i][k],map[j][k]); } } }}int getnum(int x){ int sum=0; while(x) { sum+=x%2; x/=2; } return sum;}double dfs(int now,int flag){ if(dp[now][flag]>0) return dp[now][flag]; if((1<<(now-1))==flag) { if(map[0][now]==inf) return dp[now][flag]=inf; return dp[now][flag]=map[0][now]+time[now]; } double ans=inf; int i; for(i=1;i<n;i++) { if(i!=now&&((1<<(i-1))&flag)&&map[i][now]!=inf) { ans=min(ans,time[now]+map[i][now]+dfs(i,flag^(1<<(now-1)))); } } return dp[now][flag]=ans;}int main(){ // freopen("dd.txt","r",stdin); while(scanf("%d%d%d",&n,&m,&k)) { if(n==0&&m==0&&k==0) break; k*=12; int limit=0; int i,x,j; for(i=1;i<=m;i++) { scanf("%d",&x); if(x!=1) limit+=1<<(x-2); } memset(dp,0,sizeof(dp)); for(i=0;i<n;i++) { for(j=0;j<n;j++) map[i][j]=inf; } for(i=0;i<n;i++) { map[i][i]=0; scanf("%d",&time[i]); } int a,b,d,t; while(scanf("%d%d%d%d",&a,&b,&d,&t)) { if(a+b+d+t==0) break; if(t) { double tt=(double)d/120; if(map[a-1][b-1]>tt) map[a-1][b-1]=map[b-1][a-1]=tt; } else { double tt=(double)d/80; if(map[a-1][b-1]>tt) map[a-1][b-1]=map[b-1][a-1]=tt; } } floyd(); int tmp=(1<<(n-1))-1; int ans=-1; for(i=0;i<=tmp;i++) { if((i&limit)==limit) { for(j=1;j<n;j++) { if(map[j][0]!=inf&&(i&(1<<(j-1)))) { if(map[j][0]+dfs(j,i)+time[0]<=(double)k) { ans=max(ans,getnum(i)+1); } } } } } if(ans==-1) printf("No Solution\n"); else printf("%d\n",ans); } return 0;}