题意:
给你一张图,找到一条Hamilton路,要求从其中一个节点出发,遍历图中所有节点一遍。
这样一条路径的权值计算为三部分的累加。
第一部分:这条路径上所有节点的权值之和。
第二部分:这条路径上相邻两点的乘积。
第三部分:这条路径上相邻三个点的乘积。(如果三个点互相直接相连)
如,a1、a2、a3、a4这四个点形成的一条路径,第一部分为w1+w2+w3+w4,第二部分为w1*w2+w2*w3+w3*w4,第三部分为如果a1和a3直接相连,a2和a4直接相连,则为a1*a2*a3+a2*a3*a4。
要求输出按照上述计算方法所能找到的最大的Hamilton路,并且输出这条路的个数,n <= 13。
思路:
因为这样一条路径的更新涉及到最后两个点,所以三维dp肯定要记录最后两个节点了,然后第一维用状态压缩记录当前走过的节点,第二维记录这条路径最后一个节点,第三维记录倒数第二个节点。
dp[i][j][k]表示当前状态为i,最后一个节点为j,倒数第二个节点为k。
然后dp[i][j][k]去更新dp[i|(1<<p)][p][j],四个for循环结束本题。
本题细节较多,比如num会爆int,可能会出现0 x这样的答案等等,详见代码。
总结:
此类状压dp,大都较为相似,先开一维记录状态,再根据题意,看需要记录一个数值,多记录一个数值则需要多开一维状态。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
typedef long long ll;
int n,m;
int a[20];
int w[20][20];
int dp[1<<13][15][15];
ll way[1<<13][15][15];
void solve()
{
memset(dp,-1,sizeof dp);
rep(i,0,n-1)
rep(j,0,n-1)
if(w[i][j])
dp[(1<<i)|(1<<j)][i][j] = a[i]+a[j]+a[i]*a[j],
way[(1<<i)|(1<<j)][i][j] = 1;
rep(i,0,(1<<n)-1)
rep(j,0,n-1) //最后一个
if(i&(1<<j))
rep(k,0,n-1) //倒数第二个
if((i&(1<<k)) && w[j][k] && dp[i][j][k] != -1){
// cout << "$$$$" << endl;
rep(h,0,n-1)//倒数第三个
if(!(i&(1<<h)) && w[h][j])
{
int ans = dp[i][j][k]+a[j]*a[h]+a[h];
if(w[h][k]) ans += a[k]*a[h]*a[j];
if(ans == dp[i^(1<<h)][h][j]) way[i^(1<<h)][h][j]+=way[i][j][k];
else if(ans > dp[i^(1<<h)][h][j]){
dp[i^(1<<h)][h][j] = ans;
way[i^(1<<h)][h][j] = way[i][j][k];
}
}
}
int maxn = 0;
ll num = 0;
rep(i,0,n-1)
rep(j,0,n-1)
if(w[i][j]){
if(maxn < dp[(1<<n)-1][i][j])
maxn = dp[(1<<n)-1][i][j], num = way[(1<<n)-1][i][j];
else if(maxn == dp[(1<<n)-1][i][j]) num += way[(1<<n)-1][i][j];
}
printf("%d %lld\n",maxn,num/2);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(w,0,sizeof w);
memset(way,0,sizeof way);
scanf("%d%d",&n,&m);
rep(i,0,n-1) scanf("%d",&a[i]);
if(n == 1) {
printf("%d 1\n", a[0]);
continue ;
}
rep(i,1,m)
{
int x,y;
scanf("%d%d",&x,&y);
x--, y--;
w[x][y] = 1, w[y][x] = 1;
}
solve();
}
return 0;
}
/*
dp[1<<13][15][15]:last next
*/