筆記-最小斯坦納樹
阿新 • • 發佈:2022-06-05
模擬賽遇到又爆零了。
正文
給定 \(G=(V,E)\) 和點集 \(S\),讓你求一個連通塊包含 \(S\) 內所有點且邊權最小。
首先最後的連通塊一定是一棵樹,證明顯然。
所以我們不妨欽定一個根,考慮dp。設 \(f[i,s]\) 代表以 \(i\) 為根,考慮 \(s\) 裡的點形成一個連通塊(雖然是連通塊,但是因為保證了最小所以是棵樹)。
通過兩種方法轉移:
-
假設根的度數為 \(1\),那麼可以從別的根轉移過來 \(f[i,s]=\min f[j,s]+w(j,i)]\)。
-
考慮合併 \(i\) 的兩的部分,\(f[i,s]=\min f[i,s]+f[i,s-t]\)
對於第二種操作我們列舉子集,所以要求我們從小到大列舉。
對於第一種操作很像最短路的鬆弛操作,所以直接跑最短路。
時間複雜度 \(O(3^{\mid S\mid}\mid V\mid^2+2^{\mid S\mid}\mid V\mid\mid E\mid)\)
卡常
一些卡常技巧可以讓你跑 \(9e8\)。
首先讓 dp 陣列訪問連續,也就是第一維是 \(s\),第二維是 \(i\),其次用 spfa 可以讓平均複雜度降低。
Code
#include<bits/stdc++.h> #define pii std::pair<int,int> namespace _name{ const int maxn=200,mod=998244353,inf=0x3f3f3f3f; template<typename T> inline void read(T &x){ T flag=1; char ch=getchar(); for(;!isdigit(ch);ch=getchar())if(ch=='-')flag=-1; for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0'; x*=flag; } inline void cmax(int &a,int b){if(b>a)a=b;} inline void cmin(int &a,int b){if(b<a)a=b;} }using namespace _name; int n,m,k,f[1<<14][200]; std::vector<pii>vec[maxn]; std::queue<int>q; bool vis[maxn]; void spfa(int s){ for(int i=0;i<n;i++)if(f[s][i]!=inf)q.emplace(i),vis[i]=1; while(!q.empty()){ int u=q.front();q.pop();vis[u]=0; for(auto [v,w]:vec[u]){ if(f[s][u]+w<f[s][v]){ f[s][v]=f[s][u]+w; if(!vis[v])q.emplace(v),vis[v]=1; } } } } int main(){ read(n);read(m);read(k); for(int i=1,u,v,w;i<=m;i++){ read(u);read(v);read(w);u--;v--; vec[u].emplace_back(v,w);vec[v].emplace_back(u,w); } memset(f,0x3f,sizeof(f)); for(int i=0,x;i<k;i++){ read(x);x--; f[1<<i][x]=0; } for(int s=1;s<(1<<k);s++){ for(int t=s&(s-1);t;t=(t-1)&s){ if(t>(s^t)){ for(int i=0;i<n;i++)cmin(f[s][i],f[t][i]+f[s^t][i]); } } spfa(s); } int ans=inf; for(int i=0;i<n;i++)ans=std::min(ans,f[(1<<k)-1][i]); printf("%d\n",ans); return 0; }