1. 程式人生 > 實用技巧 >POJ2288 Islands and Bridges

POJ2288 Islands and Bridges

題意描述

Islands and Bridges

給你一個包含 \(n\) 個點 \(m\) 條雙向邊的圖。

求一條路徑使得每個點恰好遍歷一遍且這條路徑的價值最大。

路徑價值的計算方式為:所有點權之和+相鄰點權之積+三角形的三個點權之積。

當然,可能存在多種價值相同的路徑,所以要求求出最大價值和最大價值的不同路徑數量。

順序不同但是經過的路徑相同的路徑被認為是同一路徑。

題目存在多組資料。

演算法分析

看到 \(n\leq 14\) 就想到狀壓...。

狀態設計

既然是狀壓就肯定有一維表示狀態啊。

然後因為價值計算與前兩個點有關,所以還要建立兩個維度。

\(f(s,i,j)\) 表示:當前點的遍歷狀況為 \(s\)

,前一個點為 \(i\),前前個點為 \(j\) 的最大價值。

預處理

當讀入一條邊 \(edge(u,v)\) 時:

\(f((u<<1|v<<1),u,v)=f((u<<1|v<<1),v,u)=a_u+a_v+a_u\times a_v\)

其餘為 \(-INF\)

狀態轉移方程

\(f(s,i,j)=max_{s>>i,j,k\ \&\ 1}\{f(s\ xor\ 1<<i,j,k)+val(i,j,k)\}\)

\(val(i,j,k)=a_i+a_i\times a_j+(triangle)a_i\times a_j\times a_k\)

答案統計

\(ans=max\{f((1<<n-1),i,j)\}\)

但是題目還要求求出路徑數量,那麼就建立 \(path\) 陣列與 \(f\) 陣列一同計算即可。

最後記得路徑數量 \(\div 2\)。(一條路徑必然被重複統計兩次)

程式碼實現

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define N 14
using namespace std;
typedef long long ll;

int Q,n,m,a[N];
ll f[1<<N][N][N],path[1<<N][N][N];
bool mp[N][N];

int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
	while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
	return x*f;
}

void pre_work(){
	memset(f,-1,sizeof(f));
	memset(path,0,sizeof(path));
	memset(mp,false,sizeof(mp));
	return;
}

void work(){
	n=read(),m=read();
	for(int i=0;i<n;i++)
		a[i]=read();
	if(n==1){//特判省時間。
		printf("%d 1\n",a[0]);
		return;
	}
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		mp[u-1][v-1]=mp[v-1][u-1]=true;
	}
	for(int i=0;i<n;i++)//預處理。
		for(int j=0;j<n;j++){
			if(i==j || !mp[i][j]) continue;
			f[(1<<i)|(1<<j)][i][j]=a[i]+a[j]+a[i]*a[j];
			path[(1<<i)|(1<<j)][i][j]=1;
		}
	for(int s=0;s<(1<<n);s++){//簡簡單單的 DP。
		for(int i=0;i<n;i++) if((s>>i)&1)
			for(int j=0;j<n;j++) if((s>>j)&1 && i!=j && mp[i][j])
				for(int k=0;k<n;k++) if((s>>k)&1 && i!=k && j!=k && mp[j][k])
					if(f[s^(1<<i)][j][k]!=-1){
						ll tmp=f[s^(1<<i)][j][k]+a[i]+a[i]*a[j];
						if(mp[k][i]) tmp+=a[i]*a[j]*a[k];
						if(tmp>f[s][i][j]){
							f[s][i][j]=tmp;
							path[s][i][j]=path[s^(1<<i)][j][k];
						}
						else if(tmp==f[s][i][j])
							path[s][i][j]+=path[s^(1<<i)][j][k];
					}
	}
	ll ans=-1,num=0;
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(i==j) continue;
			if(f[(1<<n)-1][i][j]>ans){
				ans=f[(1<<n)-1][i][j];
				num=path[(1<<n)-1][i][j];
			}
			else if(f[(1<<n)-1][i][j]==ans)
				num+=path[(1<<n)-1][i][j];
		}
	if(ans==-1) puts("0 0");//記得特判。
	else printf("%lld %lld\n",ans,num>>1);
	return;
}

int main(){
	Q=read();
	while(Q--){
		pre_work();//多組資料記得清零。
		work();
	}
	return 0;
}

完結撒花