P3959 [NOIP2017 提高組] 寶藏 題解(狀壓dp)
阿新 • • 發佈:2021-08-24
題目連結
題目大意
給定一個 n 個點 m 條邊的圖,請你求出一個有根樹。
滿足每個點的深度和它到父節點的邊權乘積之和最小。
n ≤ 12,m ≤ 1000
題目思路
本來我想的是設\(dp[i][s]\)表示以\(i\)為根節點,集合為\(s\)的最小答案但是發現根本轉移不了
考慮到點數只有12個,可以考慮狀態壓縮 DP。 用 s 表示當前加入的點集。
為了方便轉移,我們不記錄根是誰,而是直接去考慮深度。
也就是用$ dp[i][s] $表示當前的點集是 s,最深的點為 i。
然後我們去列舉 s 的補集的子集 t,把 t 都作為第 i+1 層加入 s。
我的程式碼複雜度為\(3^n*n+2^n*n^3\)
但是其實預處理\(dis\)陣列可以一次預處理出來,而我處理了\(n\)次,可以再優化一點
程式碼
卷也卷不過,躺又躺不平#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<cstdio> #include<vector> #include<string> #include<cstring> #include<iostream> #include<algorithm> #define fi first #define se second #define debug cout<<"I AM HERE"<<endl; using namespace std; typedef long long ll; const int maxn=12+5,inf=0x3f3f3f3f,mod=1e9+7; const double eps=1e-6; int n,m; int e[maxn][maxn]; int dis[maxn]; int dp[maxn][1<<12]; signed main(){ scanf("%d%d",&n,&m); memset(e,0x3f,sizeof(e)); for(int i=1,u,v,w;i<=m;i++){ scanf("%d%d%d",&u,&v,&w); u--,v--; e[v][u]=min(e[v][u],w); e[u][v]=e[v][u]; } memset(dp,0x3f,sizeof(dp)); for(int i=0;i<=n-1;i++){ dp[0][1<<i]=0; } for(int i=0;i<=n-2;i++){ for(int sta=0;sta<=(1<<n)-1;sta++){ if(dp[i][sta]==inf) continue; for(int j=0;j<n;j++){ dis[j]=inf; for(int k=0;k<n;k++){ if(sta&(1<<k)){ dis[j]=min(dis[j],e[j][k]); } } } int zi=(((1<<n)-1)^sta); for(int j=zi;;j=((j-1)&zi)){ int sum=0; for(int k=0;k<n;k++){ if(j&(1<<k)){ sum+=dis[k]; } if(sum>=inf) break; } if(sum<inf){ dp[i+1][sta|j]=min(dp[i+1][sta|j],dp[i][sta]+sum*(i+1)); } if(!j) break; } } } int ans=inf; for(int i=0;i<=n-1;i++){ ans=min(ans,dp[i][(1<<n)-1]); } printf("%d\n",ans); return 0; }