最近做的状态压缩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 2100000000 using 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; }
也是类似于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 2100000000 using 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; }
还是类似于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 long using 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; }