http://poj.org/problem?id=2288
题意:
在一个无向图中G(V,E)中(|V|<=13),每一个点有一个点权,并有哈密顿路径(指从起点开始不重复经过一点到终点的路径)。
代价:
1.所有的点权之和;
2.哈密顿路径中相邻点点权乘积之和
3.哈密顿路径中相邻的三个点若形成环,则3的答案加上这三个点的乘积。
例如:
分析:
状压dp,状压所有的情况,处理添加一个点进入路径的所有情况
d[k][i][S+{k}]=max{ d[i][j][S]+vk+vk*vi+get(k,i,j) | j!=i且i,j∈S,k∈S}.
get(k,i,j)在k,i,j三点构成三角形的时候返回vk*vi*vj,否则返回0.
初值:d[i][j][{i,j}]=vi+vj+vi*vj.i!=j.0<=i,j<=n-1.原题中说编号从1到n,程序中我们使用编号从0到n-1.
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m;
int v[15];
int d[15][15][1<<13];///第一维是终点,第二位是起点
long long num[15][15][1<<13];
int g[15][15];///用于保存初始的图 g[i][j]=1 则表示i到j有路 为0则无路
int mi[15];
int base[20000][15];
inline int get(int k,int i,int j)///如果从k到j有边,则返回这个三角形的权值
{
if(g[k][j])return v[k]*v[i]*v[j];
return 0;
}
int main()
{
int temp=1;
for(int i=0;i<=13;i++)
{
mi[i]=temp;
temp *=2;
}
memset(base,0,sizeof(base));
for(int value=0;value<mi[13];value++)///预先计算base[x][i]=1表示x的二进制第i位是1
{
temp = value;
int p=0;///第p位
while(temp>0)
{
base[value][p++]=temp%2;
temp/=2;
}
}
int t;
while(~scanf("%d",&t)&&t)
{
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d",&v[i]);
memset(g,0,sizeof(g));
for(int i=0;i<m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
a--;b--;
g[a][b]=g[b][a]=1;
}
if(n==1)///只有一个顶点时特殊处理
{
printf("%d 1\n",v[0]);
continue;
}
memset(d,-1,sizeof(d));///因为如果初始化为0,那么无法区分非法值和合法值,最后非法值也参与运算直接导致错误的结果.
memset(num,0,sizeof(num));
for(int i=0;i<n;i++)///获取d和num的初值
for(int j=0;j<n;j++)if(i!=j && g[i][j] )///i与j之间有路
{
d[i][j][mi[i]+mi[j]]=v[i]+v[j]+v[i]*v[j];
num[i][j][mi[i]+mi[j]]=1;
}
for(int S=0;S<mi[n];S++)
{
for(int i=0;i<n;i++)if(base[S][i]==1)///经过了i
{
for(int j=0;j<n;j++)if(i!=j && base[S][j]==1 &&g[i][j])///经过节点j且i到j有边
{
if(d[i][j][S]==-1)continue; ///该值非法
for(int k=0;k<n;k++)if(base[S][k]==0 && g[k][i]) ///没有经过k且从k到i有边,从i走到k
{
if( d[k][i][S+mi[k]] < d[i][j][S]+v[k]+v[k]*v[i]+get(k,i,j) ) ///特判有三角的特殊情况
{
d[k][i][S+mi[k]]=d[i][j][S]+v[k]+v[k]*v[i]+get(k,i,j);
num[k][i][S+mi[k]] = num[i][j][S];
}
else if( d[k][i][S+mi[k]] == d[i][j][S]+v[k]+v[k]*v[i]+get(k,i,j) ) ///产生相同最大值,记录次数
{
num[k][i][S+mi[k]] += num[i][j][S];
}
}
}
}
}
long long sum = 0;///出现的次数
int max_sum=0;///出现的最大值
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)if(i!=j )///j到i的路径
{
if(max_sum < d[i][j][(1<<n)-1])
{
max_sum=d[i][j][(1<<n)-1];
sum = num[i][j][(1<<n)-1];
}
else if(max_sum == d[i][j][(1<<n)-1])
{
sum += num[i][j][(1<<n)-1];
}
}
}
printf("%d %I64d\n",max_sum,sum/2);
}
}
return 0;
}