POJ 2288 Islands and Bridges

题意:哈密尔顿路问题。n个点,每一个点有权值,设哈密尔顿路为 C1C2...Cn,Ci的权值为Vi,一条哈密尔顿路的值分为三部分计算:

1.每一个点的权值之和

2.对于图中的每一条CiCi+1,加上Vi*Vi+1

3.对于路径中的连续三个点:CiCi+1Ci+2,若在图中,三点构成三角形,则要加上Vi*Vi+1*Vi+2

求一条汉密尔顿路可以获得的最大值,并且还要输出有多少条这样的哈密尔顿路。


分析:取dp[state][i][j]表示state状态下倒数第二个岛为i,最后一个岛为j时的最优解,num[state][i][j]为相应的路径数目,其中state的二进制表示的i位为1表示岛i被访问过,反之为0。

则显然当有边(i,j)存在时,有如下初值可赋:
dp[(1<<i)+(1<<j)][i][j]=val[i]+val[j]+val[i]*val[j],num[(1<<i)+(1<<j)][i][j]=1。
如果状态(state,i,j)可达,检查岛k,如果此时k没有被访问过并且有边(j,k)存在,则做如下操作:
1)设tmp为下一步访问岛k时获得的总利益,r=state+(1<<k)。
2)如果tmp>dps[r][j][k],表示此时可以更新到更优解,则更新
    dp[r][j][k]=tmp,num[r][j][k]=num[state][i][j]。
3)如果tmp==dp[r][j][k],表示此时可以获得达到局部最优解的更多方式,则更新:
    num[r][j][k]+=num[state][i][j]。
最后检查所有的状态((1<<n)-1,i,j),叠加可以得到最优解的道路数。
需要注意的是,题目约定一条路径的两种行走方式算作一种,所以最终结果要除2。

Code:
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
#include <map>
#include <set>
#define LL long long
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;

int val[15];
bool Map[13][13];
int dp[1<<13][13][13];
LL num[1<<13][13][13];
int n,m;

int main()
{
    int cas;
    scanf("%d",&cas);
    while(cas--){
        scanf("%d %d",&n,&m);
        for(int i=0;i<n;i++)
            scanf("%d",&val[i]);
        int u,v;
        memset(Map,false,sizeof(Map));
        while(m--){
            scanf("%d %d",&u,&v);
            u--;v--;
            Map[u][v]=Map[v][u]=true;
        }
        if(n==1){
            printf("%d 1\n",val[0]);
            continue;
        }
        memset(dp,-1,sizeof(dp));
        memset(num,0,sizeof(num));
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(i!=j&&Map[i][j]){
                    dp[(1<<i)|(1<<j)][i][j]=val[i]+val[j]+val[i]*val[j];
                    num[(1<<i)|(1<<j)][i][j]=1;
                }
            }
        }
        for(int s=0;s<(1<<n);s++){//枚举状态
            for(int i=0;i<n;i++){//枚举集合s中倒数第二个点
                if(s&(1<<i)){
                    for(int j=0;j<n;j++){//枚举集合s中的倒数第一个点
                        if((s&(1<<j))&&(i!=j)&&Map[i][j]&&dp[s][i][j]!=-1){
                            for(int k=0;k<n;k++){//枚举集合s外的一点
                                if(i!=k&&j!=k&&Map[j][k]&&(!(s&(1<<k)))){
                                    int tmp=dp[s][i][j]+val[k]+val[j]*val[k];
                                    if(Map[i][k]) tmp+=val[i]*val[j]*val[k];
                                    if(dp[(s|(1<<k))][j][k]<tmp){
                                        dp[(s|(1<<k))][j][k]=tmp;
                                        num[(s|(1<<k))][j][k]=num[s][i][j];
                                    }
                                    else if(tmp==dp[(s|(1<<k))][j][k])
                                        num[s|(1<<k)][j][k]+=num[s][i][j];
                                }
                            }
                        }
                    }
                }
            }
        }
        int ans1=0;
        LL ans2=0;
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(i!=j&&Map[i][j]){
                    if(dp[(1<<n)-1][i][j]>ans1){
                        ans1=dp[(1<<n)-1][i][j];
                        ans2=num[(1<<n)-1][i][j];
                    }
                    else if(ans1==dp[(1<<n)-1][i][j])
                        ans2+=num[(1<<n)-1][i][j];
                }
            }
        }
        cout<<ans1<<' '<<ans2/2<<endl;
    }
    return 0;
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值