POJ2288 Islands and Bridges
阿新 • • 發佈:2020-07-30
題意描述
給你一個包含 \(n\) 個點 \(m\) 條雙向邊的圖。
求一條路徑使得每個點恰好遍歷一遍且這條路徑的價值最大。
路徑價值的計算方式為:所有點權之和+相鄰點權之積+三角形的三個點權之積。
當然,可能存在多種價值相同的路徑,所以要求求出最大價值和最大價值的不同路徑數量。
順序不同但是經過的路徑相同的路徑被認為是同一路徑。
題目存在多組資料。
演算法分析
看到 \(n\leq 14\) 就想到狀壓...。
狀態設計
既然是狀壓就肯定有一維表示狀態啊。
然後因為價值計算與前兩個點有關,所以還要建立兩個維度。
\(f(s,i,j)\) 表示:當前點的遍歷狀況為 \(s\)
預處理
當讀入一條邊 \(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; }
完結撒花