最近做的狀態壓縮DP小節:
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4257
zoj 4257
一堆氣體相互碰撞產生能量,求最後能產生的最大能量,應該是很基礎的狀態壓縮DP吧,設dp[flag]表示狀態flag時能產生的最大能量,(flag中1表示該氣體還存在,0表示該氣體已經消失)邊界條件是flag所有位都為一時,這時產生的能量為0,然後就枚舉最後剩下的氣體,求最大值即可。
#include <iostream>#include <string.h>#include <stdio.h>#include <algorithm>using namespace std;int dp[1<<10];int bo[11][11];int n;int max(int a,int b){ return a>b?a:b;}int dfs(int flag){ if(dp[flag]!=-1) return dp[flag]; int i,j; int ans=0; for(i=1;i<=n;i++) { if(flag&1<<(i-1)) { for(j=1;j<=n;j++) { if(j!=i&&(flag&1<<(j-1))==0) { ans=max(ans,dfs(flag^(1<<(j-1)))+bo[i][j]); } } } } return dp[flag]=ans;}int main(){ //freopen("dd.txt","r",stdin); while(scanf("%d",&n)&&n) { int i,j; for(i=1;i<=n;i++) { for(j=1;j<=n;j++) scanf("%d",&bo[i][j]); } memset(dp,-1,sizeof(dp)); int ans=0; dp[(1<<n)-1]=0; for(i=0;i<n;i++) { ans=max(ans,dfs(1<<i)); } printf("%d\n",ans); } return 0;}
http://acm.hdu.edu.cn/showproblem.php?pid=3001
hdu 3001:
類似於TSP問題,但是這裡不同的是每個點最多可以走兩次,而不是一次,所以我們可以用三進位來表示每一個狀態,我們設dp[now][flag]表示目前在now點狀態為flag時的最小花費,則邊界條件為當(1<<(now-1))==flag時,這時表示起點在now點,還沒開始走,則此時的最小花費是0,還有一個邊界條件為(1<<(now-1))*2==flag,這表示從now點走到now點,這是不可能的,所以把最小花費設為無窮大,剩下的就是狀態轉移了。
dp[now][flag]=dp[pre][flag']+map[pre][now],滿足以下幾個條件:
1:pre!=now且map[pre][now]不為-1(也就是存在邊)
2:flag的第pre位不為0
3:flag'的第now位比flag的第now位少1.
我們求最小值即可,我們要枚舉所有每一位都不為0的狀態,取它們的最小值即為答案。
#include <iostream>#include <string.h>#include <stdio.h>#include <algorithm>#define inf 2100000000using namespace std;int bo[11][11];int dp[11][60000];int bit[60000][11];int pow[12];void init(){ int i,j; for(i=0;i<=59048;i++) { int t=i; for(j=1;j<=10;j++) { bit[i][j]=t%3; t/=3; } } pow[1]=1; for(i=2;i<=11;i++) pow[i]=pow[i-1]*3;}int check(int x){ while(x) { if(x%3==0) return 0; x/=3; } return 1;}int n,m;int min(int a,int b){ return a<b?a:b;}int dfs(int now,int flag){ if(dp[now][flag]!=-1) return dp[now][flag]; if(pow[now]==flag) return dp[now][flag]=0; if(pow[now]*2==flag) return dp[now][flag]=inf; int ans=inf,i; for(i=1;i<=n;i++) { if(i!=now&&bit[flag][i]&&bo[i][now]!=-1) { int tmp=dfs(i,flag-pow[now]); if(tmp!=inf) { ans=min(ans,tmp+bo[i][now]); } } } return dp[now][flag]=ans;}int main(){ // freopen("dd.txt","r",stdin); init(); while(scanf("%d%d",&n,&m)!=EOF) { int i,a,b,c; memset(bo,-1,sizeof(bo)); memset(dp,-1,sizeof(dp)); for(i=0;i<m;i++) { scanf("%d%d%d",&a,&b,&c); if(bo[a][b]==-1||bo[a][b]>c) bo[a][b]=bo[b][a]=c; } int ma=pow[n+1]-1,mi=ma/2; int ans=inf; for(i=1;i<=n;i++) { for(int j=mi;j<=ma;j++) { if(check(j)) ans=min(ans,dfs(i,j)); } } if(ans==inf) ans=-1; printf("%d\n",ans); } return 0;}
http://poj.org/problem?id=3311
poj 3311
也是類似於TSP問題,不過這個時候每個點可以走無限次,所以我們要用依次floyd演算法求一下個點之間的最短路,這裡設dist[u][v]表示u和v之間的最短路,然後就和平常求TSP問題一樣了,我們還是設dp[now][flag]表示當前在now點狀態為flag的最小花費,邊界條件為flag==(1<<(now-1))時,此時表示從起點開始走第一次到達到達now,為其他店還沒開hi走,則dp[now][flag]=dist[0][now],其他情況基本和上體一樣。
注意題目要求最後一定要回到0點。
#include <iostream>#include <string.h>#include <algorithm>#include <stdio.h>#define inf 2100000000using namespace std;int map[11][11];int dp[11][1<<10];int n;int min(int a,int b){ return a<b?a:b;}int dfs(int now,int flag){ //printf("%d %d\n",now, flag); if(dp[now][flag]!=-1) return dp[now][flag]; int i,j; if(flag==(1<<(now-1))) return dp[now][flag]=map[0][now]; int ans=inf; if(now==0) { for(i=1;i<=n;i++) { ans=min(ans,dfs(i,flag)+map[i][now]); } } else { for(i=1;i<=n;i++) { if(i!=now&&flag&(1<<(i-1))) { ans=min(ans,map[i][now]+dfs(i,flag^(1<<(now-1)))); } } } return dp[now][flag]=ans;}void floyd(int n){ 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][k],map[j][i]+map[i][k]); } } }}int main(){ //freopen("dd.txt","r",stdin); int i,j; while(scanf("%d",&n)&&n) { for(i=0;i<=n;i++) { for(j=0;j<=n;j++) scanf("%d",&map[i][j]); } floyd(n); memset(dp,-1,sizeof(dp)); dp[0][0]=0; printf("%d\n",dfs(0,(1<<n)-1)); } return 0;}
poj 2288
還是類似於TSP問題,不過這裡對於花費做了新的定義,花費分為三部分
1:路徑中每個點的權值之和。
2:路徑中相鄰兩個點的權值之積的和。
3:若存在路徑pi->pi+1->pi+2,且pi和pi+2之間有邊,則花費還要加上這三點的權值之和。
我們要求花費最大的路徑以及這樣的路徑的個數
所以我們社狀態時不能只考慮當前點,還要考慮之前的點了,所以我們設dp[now][pre][flag]表示當前點在now,上一個點在pre,此時狀態為flag時的最大花費。設way[now][flag][flag]為dp[now][pre][flag]取最大值時的路徑條數。則接下來我們就和求TSP問題時差不多了,只是求花費時稍微麻煩一點,邊界條件是當now為起點時,即(1<<(now-1))==flag時,此時花費為now點的權值,此時的路徑條數為1.代碼還有一些細節,具體實現請參考代碼。
#include <iostream>#include <string.h>#include <algorithm>#include <stdio.h>#define maxn 100010#define ll long longusing namespace std;ll dp[14][14][1<<13];ll way[14][14][1<<13];int bo[14][14];int v[14];void init(){ memset(bo,0,sizeof(bo)); memset(way,0,sizeof(way)); memset(dp,-1,sizeof(dp));}ll max(ll a,ll b){ return a>b?a:b;}int n;ll dfs(int now,int pre,int flag){ // printf("f"); if(dp[now][pre][flag]!=-1) return dp[now][pre][flag]; if(1<<(now-1)==flag) { way[now][pre][flag]=1; return dp[now][pre][flag]=v[now]; } int i,tru=0; ll ans=0; for(i=1;i<=n;i++) { if(i!=now&&i!=pre&&(flag&(1<<(i-1)))) { tru=1; if(bo[i][pre]) { ll tmp=0; tmp=dfs(pre,i,flag^(1<<(now-1))); if(tmp) { tmp+=v[now]+v[now]*v[pre]; if(bo[i][now]) tmp+=v[now]*v[pre]*v[i]; ans=max(ans,tmp); } } } } if(!tru) { dp[now][pre][flag]=dfs(pre,0,flag^(1<<(now-1)))+v[now]+v[now]*v[pre]; way[now][pre][flag]=1; return dp[now][pre][flag]; } if(ans) { for(i=1;i<=n;i++) { if(i!=now&&i!=pre&&bo[i][pre]&&(flag&(1<<(i-1)))) { ll tmp=0; tmp=dfs(pre,i,flag^(1<<(now-1))); if(tmp) { tmp+=v[now]+v[now]*v[pre]; if(bo[i][now]) tmp+=v[now]*v[pre]*v[i]; if(ans==tmp) { way[now][pre][flag]+=way[pre][i][flag^(1<<(now-1))]; } } } } } return dp[now][pre][flag]=ans;}int main(){ //freopen("dd.txt","r",stdin); int ncase; scanf("%d",&ncase); while(ncase--) { int m,i,a,b,j; init(); scanf("%d%d",&n,&m); for(i=1;i<=n;i++) scanf("%d",&v[i]); for(i=0;i<m;i++) { scanf("%d%d",&a,&b); bo[a][b]=bo[b][a]=1; } ll ans=0,num=0; if(n==1) printf("%d 1\n",v[1]); else { for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(i!=j&&bo[i][j]) { dfs(i,j,(1<<n)-1); ans=max(ans,dp[i][j][(1<<n)-1]); } } } if(ans==0) printf("0 0\n"); else { for(i=1;i<=n;i++) { for(j=1;j<=n;j++) { if(i!=j&&bo[i][j]&&dp[i][j][(1<<n)-1]==ans) { num+=way[i][j][(1<<n)-1]; } } } printf("%I64d %I64d\n",ans,num/2); } } } return 0;}