POJ2288

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;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值