Islands and bridges

Description

给定一些岛屿和一些连接岛屿的桥梁,大家都知道汉密尔顿路是访问每个岛屿一次的路线,在我们这个地图中,每个岛屿有个正整数的权值,表示这个岛屿的观赏价值。假设一共有N个岛屿,用Vi表示岛屿Ci的价值,汉密尔顿路 C 1 C 2 . . . . C n C_1C_2....C_n C1C2....Cn 的价值是以下三部分的总和:

(1)所有岛屿的价值之和;

(2)对于路径中相邻的两个岛屿 C i , C i + 1 C_i,C_i+1 Ci,Ci+1 ,把两个岛屿的价值之积加到总价值中;

(3)路径中连续三个岛屿 C i , C i + 1 , C i + 2 C_i,C_i+1,C_i+2 Ci,Ci+1,Ci+2 ,如果 C i C_i Ci C i + 2 C_i+2 Ci+2 有桥直接相连,则把这三个岛屿价值之积加到总价值中。

要求计算汉密尔顿路最大的价值以及方案数

Input

输入第一行是一个整数 Q ( Q < = 20 ) Q(Q<=20) Q(Q<=20) ,表示测试数据的数量。每个测试数据第一行输入两个整数 N N N M M M ,分别表示岛屿数和桥梁数,接下来一行包含 N N N 个正整数,第i个数表示 V i V_i Vi ,每个数不超过 100 100 100 ,最后 M M M 行,每行两个数 X , Y X,Y X,Y ,表示岛 X X X 和岛 Y Y Y 之间有一座桥直接相连,桥是双向的,岛屿编号为 1 1 1 N N N ( N < = 13 ) (N<=13) (N<=13)

Output

对于每个测试数据,输出一行,两个整数,第一个数表示最大价值,第二个数表示方案数,如果不存在汉密尔顿路径,输出“0 0”。

注意:一条路径可以反着走,我们认为这两条路径是同一条路径。

Sample Input

2
3 3
2 2 2
1 2
2 3
3 1
4 6
1 2 3 4
1 2
1 3
1 4
2 3
2 4
3 4

Sample Output

22 3
69 1

根据题目统计答案的规则,我们需要记录上一个和上上个走到的点,所以我们设 f [ s ] [ i ] [ j ] f[s][i][j] f[s][i][j] 表示当前走过点的状态为 s s s(1表示走过,0表示没走过),上一个走到的是点i,上上个走到的是点j之后时最大的总价值是多少,于是我们就可以先预处理出经过两个点的状态,然后枚举每一个状态暴力DP转移就行了
至于统计方案数也很简单,设一个 g [ s ] [ i ] [ j ] g[s][i][j] g[s][i][j] 表示在 f [ s ] [ i ] [ j ] f[s][i][j] f[s][i][j] 情况下的方案数,转移时只要判断一下有没有大于原来的 f f f ,如果大于则 g g g 就赋值成转移过来的那个状态的 g g g ,如果等于就加上转移过来那个状态的 g g g 就行了
之后注意当只有一个岛的时候是可以走完的,需要特判一下

#include<bits/stdc++.h> //islands and bridges
using namespace std;
int q,n,m,ans1,a[15],f[1<<(15-1)][15][15];
long long g[1<<(15-1)][15][15],ans2;
bool b[15][15];
int main(){
	scanf("%d",&q);
	while(q--){
		scanf("%d%d",&n,&m);
		for(register int i=1;i<=n;++i) scanf("%d",&a[i]);
		if(n==1){printf("%d 1\n",a[1]);continue;}
		memset(b,0,sizeof(b));
		for(register int i=1;i<=m;++i){
			int x,y; scanf("%d%d",&x,&y); b[x][y]=b[y][x]=1;
		}
		memset(f,0,sizeof(f));memset(g,0,sizeof(0));
		for(register int i=1;i<=n;++i)
			for(register int j=1;j<=n;++j) if(i!=j&&b[i][j]) f[(1<<i-1)|(1<<j-1)][i][j]=a[i]*a[j]+a[i]+a[j],g[(1<<i-1)|(1<<j-1)][i][j]=1;
		int tot=(1<<n)-1;
		for(register int s=0;s<=tot;++s)
			for(register int i=1;i<=n;++i)
				if((s&(1<<i-1)))
					for(register int j=1;j<=n;++j)
						if(j!=i&&s&(1<<j-1)&&b[i][j])
							for(register int k=1;k<=n;++k)
								if(k!=i&&k!=j&&s&(1<<k-1)&&b[k][j]){
									int now=s-(1<<i-1);
									if(f[now][j][k]){
										int num=f[now][j][k]+a[i]*a[j]+a[i];
										if(b[i][k]) num+=a[i]*a[j]*a[k];
										if(num>f[s][i][j]) f[s][i][j]=num,g[s][i][j]=g[now][j][k];
										else if(num==f[s][i][j]) g[s][i][j]+=g[now][j][k];
									}
								}
		ans1=ans2=0;
		for(register int i=1;i<=n;++i)
			for(register int j=1;j<=n;++j)
				if(i!=j){
					if(f[tot][i][j]>ans1) ans1=f[tot][i][j],ans2=g[tot][i][j];
					else if (f[tot][i][j]==ans1) ans2+=g[tot][i][j];
				}
		printf("%d %lld\n",ans1,ans1>0?ans2>>1:0);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值