n个点,m条无向边,问是否存在哈密顿路,若存在,最大的边权和是多少?与之对应的路的条数有多少条?
边权和的计算方法:所有顶点的权值+相邻顶点权值的乘积
若相邻三个点两两之间都有边,则还需加上它们的权值的乘积
考虑用一个二进制位表示某个顶点是否被访问。用n个二进制位表示一个状态。
注意到,每一个状态和前一个走过的顶点以及上上个走过的顶点有关。
因此,设dp[s][i][j]表示在状态s下,经过顶点j,到达顶点i的最大权值。
那么状态转移方程:
dp[s][i][j]=max(dp[s][i][j],dp[s′][j][k]+a[i]+a[i]∗a[j]+tmp)
说明:s’为上一个状态,i为当前枚举的要到达的顶点,j为前一个状态已经到达的顶点,k为上上个走过的顶点。a[i]表示顶点i的权值,若i、j、k两两之间都由边,则tmp=a[i]*a[j]*a[k],否则tmp=0。
初始化:首先初始化为-1,表示未定义。然后枚举两个顶点i,j。dp[s][i][j]=a[i]+a[j]+a[i]*a[j]。s=2^(i-1)+2^(j-1)
对于路径条数的统计,同样地,设num[s][i][j]表示在状态s下经过j到达i的路径条数。在更新dp的同时,更新num即可。
注意:
1、数据会超出int的范围。
2、只有1个顶点的情况。
3、当最终dp的值为-1时,说明不存在哈密顿路,输出0 0。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define maxn 14
typedef __int64 LL;
bool Map[maxn][maxn];
LL a[maxn],dp[1<<13][maxn][maxn],num[1<<13][maxn][maxn];
int main()
{
int q,n,m,i,j,k,s;
scanf("%d",&q);
while(q--)
{
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i) scanf("%I64d",&a[i]);
memset(Map,0,sizeof(Map));
for(i=0;i<m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
Map[x][y]=Map[y][x]=1;
}
if(n==1) {printf("%I64d 1\n",a[1]); continue;}
memset(dp,-1,sizeof(dp));
for(i=1;i<=n;++i)
for(j=1;j<=n;++j)
if(Map[i][j]){
num[(1<<(i-1))+(1<<(j-1))][i][j]=1;
dp[(1<<(i-1))+(1<<(j-1))][i][j]=a[i]+a[j]+a[i]*a[j];
}
for(s=0;s<(1<<n);++s)
for(i=1;i<=n;++i)
if(s&(1<<(i-1)))
for(j=1;j<=n;++j)
{
if(!Map[j][i]) continue;
if(s&(1<<(j-1)))
{
for(k=1;k<=n;++k)
{
if(!Map[j][k]) continue;
if(k==i) continue;
if(s&(1<<(k-1)))
{
if(dp[s-(1<<(i-1))][j][k]==-1) continue;
LL tmp=dp[s-(1<<(i-1))][j][k]+a[i]+a[i]*a[j];
if(Map[i][k]) tmp+=a[i]*a[j]*a[k];
if(dp[s][i][j]<tmp)
{
dp[s][i][j]=tmp;
num[s][i][j]=num[s-(1<<(i-1))][j][k];
}
else if(dp[s][i][j]==tmp)
num[s][i][j]+=num[s-(1<<(i-1))][j][k];
}
}
}
}
LL ans=-1,cnt;
for(i=1;i<=n;++i)
for(j=1;j<=n;++j)
if(ans<dp[(1<<n)-1][i][j])
{
ans=dp[(1<<n)-1][i][j];
cnt=num[(1<<n)-1][i][j];
}
else if(ans==dp[(1<<n)-1][i][j]) cnt+=num[(1<<n)-1][i][j];
if(ans!=-1) printf("%I64d %I64d\n",ans,cnt>>1);
else puts("0 0");
}
return 0;
}