POJ2288Islands and Bridges
现在有n个点和m条边的无向图,每个节点有一个权值vi,我们要求这个图的最优三角哈密顿路,哈密顿路是一条经过所有节点一次的路。
最优三角哈密顿路是这么定义的:对于一条哈密顿路c1c2…cn,我们首先令sum = sum(vi),1<=i<=n.然后我们在令sum +=vi*vi+1,1<=i<=n-1.最后 如果ci与ci+2之间有路,则令sum+=vi*vi+1*vi+2,1<=i<=n-2.
对于所有存在的哈密顿路,具有最大sum值的那些就是最优三角哈密顿路。
输入:首先是T(1<=T<=20)表实例数.接下来是T个实例,对于每个实例,第一行是n(1<=n<=13)和m.接下来一行有n个小于等于100的正整数,分别表示vi权值。接下来m行,每行有2个整数x和y即节点编号(从1到n编号),表示从x节点到y节点有一条无向路。
输出:输出对应的最优哈密顿路的值sum和具有sum的哈密顿路的条数。对于一条哈密顿路,它的逆序和它本身只能算是一条。
如果不存在哈密顿路,输出0 0.
分析:求哈密顿路的最短路长好求,直接用类似TSP货郎问题的状态来表示即可。令d[i][S]=x表示当前在i点,已经走过了集合S中的点,生成的当前路的总长度为x。但是哈密顿路的sum值不好求,因为当前sum值最大并不一定子问题的sum值也最大,求sum值问题即不具有最优子结构.
既然节点最多只有13个,那么我们用生成这13个节点的全排列,然后求出所有排列的sum值,然后在找最大sum并且计数,这样行不行? 不行,13!=6227020800,62亿多如果一个个枚举肯定超时.
其实假设我们当前已经放下了集合{1,2,4,5}这4个点(次序不定),接下来我们要放1到n中的哪个点才能使得集合{1,2,4,5,x}的sum值最大呢?这里我们需要构建一个从最大子问题到最大父问题的模型.
这个值与{1,2,4,5}中的最后两个点有关,只要我们定义d[i][j][S]为当前在i点,且前一个点在j点并且走过了集合S中的所有点一次对应的当前最小sum值.那么我们根据d[i][j][S]可以算出d[k][i][S+{k}]的最小sum.这就是一个从最大到最大的模型.
d[k][i][S+{k}]=max{ d[i][j][S]+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.
最终值为:d[i][j][(1<<n)-1].其中i与j不同且需要遍历所有的i与j算出sum最大值出现的次数.
现在我们想想会不会第一个d[i][j] [(1<<n)-1]中可能包含了多次出现该值且以j和i结尾的情况?会的.所以我们要通过状态转移的过程来计数.令num[i][j][S]表示当前在i点,前一个点是j且走过了集合S中的所有点时,并在sum取最小值时,这些走过的点有多少种排列符合之前的要求.
状态转移方程: num[k][i][S+{k}]=sum{ num[i][j][S]}i,j∈S,k∉S.
初值为: num[i][j][{i,j}]=1. i!=j.0<=i,j<=n-1.原题中说编号从1到n,程序中我们使用编号从0到n-1.
num[i][j][S]是那些可以使得d[k][i][S+{k}]取得最大值的num.所以只有在d[k][i][S+{k}]取得最大sum值时才给加上这个次数.
现在有个问题num[i][1][S]和num[i][2][S]中会不会有重复的情况呢?使得最后算得的num[k][i][S+{k}]值偏大?明显不会,如果有重复的情况就是说num[k][i][S+{k}]加上了两次num[i][j][S],而j每次都是不同的,所以不可能出现重复情况.
最后求得的sum值需要除以2,因为逆序哈密顿路只算一次.
程序中有很多细节需要考虑,注意到.其中之一是
memset(d,-1,sizeof(d));因为如果初始化为0,那么无法区分非法值和合法值,最后非法值也参与运算直接导致错误的结果.
定义num数组要用long long 因为13!为60多亿超过了int能表示的范围,直接结果就是num的值有可能会越界.d数组可以用int,因为每个vi不超过100.
AC代码:563ms
#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)==1&&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)//集合S有节点i
{
for(int j=0;j<n;j++)if(i!=j && base[S][j]==1 &&g[i][j])//集合S有节点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])//S中没有k且从k到i有边
{
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 && g[i][j])//i与j之间有边
{
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;
}