poj 2288 Islands and Bridges_状态压缩dp_哈密尔顿回路问题
题目描述:哈密尔顿路问题。n个点,每一个点有权值,设哈密尔顿路为 C1C2...Cn,Ci的权值为Vi,一条哈密尔顿路的值分为三部分计算:
1.每一个点的权值之和
2.对于图中的每一条CiCi+1,加上Vi*Vi+1
3.对于路径中的连续三个点:CiCi+1Ci+2,若在图中,三点构成三角形,则要加上Vi*Vi+1*Vi+2
求一条汉密尔顿路可以获得的最大值,并且还要输出有多少条这样的哈密尔顿路。
这道题的状态感觉不是很难想,因为根据一般的哈密尔顿路问题,首先想到的是设计二维状态,dp[i , s]表示当前在i点,走过的点形成状态集合s。但是这道题在求解值的时候有一个不一样的地方,就是第三部分,如果还是设计成二维的状态,就会很麻烦,因为每加入一个新点,要判断新点、当前点、倒数第二个点是否构成三角形,所以要记录倒数第二个点。很自然地想到扩展状态的维数,增加一维,记录倒数第二个点。
1> 设计状态:
dp[i , j , s]表示当前站在j点,前一个点是i点,形成的状态集合是s,此时的最大值,way[i , j , s]记录当前状态下达到最大值的路径数;
2> 状态转移:
设k点不在集合s中,且存在边<j , k>
设q为下步到达k点获得的最大值
令r = s + (1<<k),为当前站在点k,前一个点为j,形成状态集合r
若i,j,k形成三角形,则q = dp[i][j][s] + v[k] + v[j]*v[k] + v[i]*v[j]*v[k]
否则,q = dp[i][j][s] + v[k] + v[j]*v[k];
若q大于dp[j][k][r];则:
dp[j][k][r] = q
way[j][k][r] = way[i][j][s];
若q等于dp[j][k][r],则:
way[j][k][r] += way[i][j][s];
3> 初始化:
显然,若i点到j点有边,则:
dp[i][j][(1<<i)+(1<<j)] = v[i] + v[j] + v[i]*v[j];
way[i][j][(1<<i)+(1<<j)] = 1;
4> 结果的产生:
最后的结果我们要枚举点i和j,找到最大的dp[i][j][(1<<n)-1],并且更新记录路径数ansp,最后ansp要除2才是结果,因为题目最后一句话,正向反向是一样的路。
此外,需要注意的是discuss提到的特殊情况,要用__int64,并且注意n等于1时,最大值就是第一个点的权值,路径数为1。
#include <iostream> #include<cstdio> #include<cstring> using namespace std; const int MAXN =13; const int MAXS =1<<MAXN|1; #define ll __int64 ll dp[MAXN][MAXN][MAXS],way[MAXN][MAXN][MAXS]; int map[MAXN][MAXN],v[MAXN]; int n,m,s; void stateDp(){ int i,j,p,k; memset(dp,-1,sizeof(dp)); memset(way,0,sizeof(way)); for(i=0;i<n;i++) for(j=0;j<n;j++){ if(map[i][j]){ dp[i][j][(1<<i)+(1<<j)]=v[i]+v[j]+v[i]*v[j]; way[i][j][(1<<i)+(1<<j)]=1; } } for(p=3;p<s;p++){ for(i=0;i<n;i++){ if(!(p&1<<i))//如果该状态第i城市没有路过就跳过 continue; for(j=0;j<n;j++){ if(i==j||!(p&1<<j)||dp[i][j][p]==-1) continue; for(k=0;k<n;k++){ if(p&1<<k||!map[j][k])//如果k存在该状态则跳过 continue; int r=p+(1<<k);//状态加入k城市 ll q=dp[i][j][p]+v[k]+v[j]*v[k];//更新价值 if(map[i][k]){//当构成环时更新价值 q+=v[i]*v[j]*v[k]; } if(q>dp[j][k][r]){ dp[j][k][r]=q; way[j][k][r]=way[i][j][p]; }else if(q==dp[j][k][r]){//相等时,有多个相等价值路径 way[j][k][r]+=way[i][j][p]; } } } } } } int main(int argc, char** argv) { int t,x,y,i,j; scanf("%d",&t); while(t--){ memset(map,0,sizeof(map)); scanf("%d%d",&n,&m); for(i=0;i<n;i++) scanf("%d",&v[i]); for(i=0;i<m;i++){ scanf("%d%d",&x,&y); map[x-1][y-1]=map[y-1][x-1]=1; } s=1<<n; if(n==1){ printf("%d %d\n",v[0],1); continue; } stateDp(); ll ansv=-1,ansp=0; for(i=0;i<n;i++) for(j=0;j<n;j++){ if(i==j)continue; if(dp[i][j][s-1]>ansv){//s-1为经过所有岛 ansv=dp[i][j][s-1]; ansp=way[i][j][s-1]; }else if(dp[i][j][s-1]==ansv){ ansp+=way[i][j][s-1]; } } printf("%I64d %I64d\n",ansv==-1?0:ansv,ansp/2); } return 0; }