斯坦納樹
轉載註明來源:https://www.cnblogs.com/syc233/p/13650130.html
姑且當作狀壓DP的複習了。
斯坦納樹問題是組合優化問題,與最小生成樹相似,是最短網路的一種。最小生成樹是在給定的點集和邊中尋求最短網路使所有點連通。而最小斯坦納樹允許在給定點外增加額外的點,使生成的最短網路開銷最小。
用一道模板題來引入問題:
P6192 【模板】最小斯坦納樹
給定一個包含 \(n\) 個結點和 \(m\) 條帶權邊的無向連通圖 \(G=(V,E)\)。
再給定包含 \(k\) 個結點的點集 \(S\),選出 \(G\) 的子圖 \(G′=(V′,E′)\),使得:
- \(S\subseteq V'\)
- \(G'\) 為連通圖;
- \(E'\) 中所有邊的權值和最小。
求 \(E'\) 中所有邊的權值和。
為了使邊權和最小, \(G'\) 一定是一棵樹,證明類比最小生成樹。
考慮狀壓DP,令 \(f(i,s)\) 表示以 \(i\) 為根的包含點集 \(s \subseteq S\) 的樹的最小邊權和。則有轉移:
- 若 \(i\) 的度數為 \(1\) ,則列舉與它有邊相連的點轉移, \(f(i,s)=\min_{j}f(j,s)+w(i,j)\) 。
- 若 \(i\) 的度數大於 \(1\) ,則劃分成幾個子樹求解, \(f(i,s)=\min_{t \subseteq s}f(i,t)+f(i,s-t)\)
第一種轉移可以每次跑最短路實現。
\(\text{Code}:\)
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <cmath> #include <queue> #include <vector> #define maxn 105 #define maxm 505 #define Rint register int #define INF 0x3f3f3f3f using namespace std; typedef long long lxl; template <typename T> inline void read(T &x) { x=0;T f=1;char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} x*=f; } struct edge { int u,v,w,next; }e[maxm<<1]; int head[maxn],k; inline void add(int u,int v,int w) { e[k]=(edge){u,v,w,head[u]}; head[u]=k++; } int n,m,K; int f[maxn][1<<11]; bool vis[maxn]; typedef pair<int,int> pii; priority_queue<pii,vector<pii>,greater<pii> > q; inline void Dijkstra(int S) { memset(vis,0,sizeof(vis)); while(!q.empty()) { int u=q.top().second;q.pop(); if(vis[u]) continue; vis[u]=true; for(int i=head[u];~i;i=e[i].next) { int v=e[i].v; if(f[v][S]>f[u][S]+e[i].w) { f[v][S]=f[u][S]+e[i].w; q.push(make_pair(f[v][S],v)); } } } } int p[11]; int main() { // freopen("P6192.in","r",stdin); read(n),read(m),read(K); memset(head,-1,sizeof(head)); for(int i=1,u,v,w;i<=m;++i) { read(u),read(v),read(w); add(u,v,w);add(v,u,w); } memset(f,0x3f,sizeof(f)); for(int i=1;i<=K;++i) { read(p[i]); f[p[i]][1<<(i-1)]=0; } for(int S=1;S<(1<<K);++S) { for(int i=1;i<=n;++i) { for(int S0=S&(S-1);S0;S0=(S0-1)&S&(S-1)) f[i][S]=min(f[i][S],f[i][S0]+f[i][S^S0]); if(f[i][S]!=INF) q.push(make_pair(f[i][S],i)); } Dijkstra(S); } printf("%d\n",f[p[1]][(1<<K)-1]); return 0; }
練習
P4294 [WC2008]遊覽計劃
棋盤圖上求解最小斯坦納樹。由於這道題是點權,所以轉移方程有所不同:
\[\begin{aligned} &f(i,s)=\min f(j,s)+a_i\\ &f(i,s)=\min_{t \subseteq s} f(i,t)+f(i,s-t)-a_i \end{aligned} \]
另外這題需要輸出方案,所以對每一個狀態都要記錄前驅,略噁心。
\(\text{Code}:\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <queue>
#include <vector>
#define maxn 105
#define maxm 2005
#define Rint register int
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;
template <typename T>
inline void read(T &x)
{
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
x*=f;
}
struct edge
{
int u,v,next;
}e[maxm<<1];
int head[maxn],k;
inline void add(int u,int v)
{
e[k]=(edge){u,v,head[u]};
head[u]=k++;
}
typedef pair<int,int> pii;
pii pre[maxn][1<<11];
int n,m,K,a[maxn],p[maxn];
int f[maxn][1<<11];
bool vis[maxn];
priority_queue<pii,vector<pii>,greater<pii> > q;
inline void Dijkstra(int S)
{
memset(vis,0,sizeof(vis));
while(!q.empty())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=true;
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].v;
if(f[v][S]>f[u][S]+a[v])
{
f[v][S]=f[u][S]+a[v];
pre[v][S]=make_pair(u,S);
q.push(make_pair(f[v][S],v));
}
}
}
}
inline int pos(int x,int y) {return (x-1)*m+y;}
int ans[15][15];
inline void dfs(int u,int S)
{
if(!pre[u][S].second) return;
ans[(u-1)/m+1][(u-1)%m+1]=1;
if(pre[u][S].first==u) dfs(u,S^pre[u][S].second);
dfs(pre[u][S].first,pre[u][S].second);
}
int main()
{
// freopen("P4294.in","r",stdin);
read(n),read(m);
memset(head,-1,sizeof(head));
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
int u=pos(i,j);
read(a[u]);
if(!a[u])
{
p[++K]=u;
f[p[K]][1<<(K-1)]=0;
}
if(i-1>=1)
{
add(u,pos(i-1,j));
add(pos(i-1,j),u);
}
if(j-1>=1)
{
add(u,pos(i,j-1));
add(pos(i,j-1),u);
}
}
for(int S=1;S<(1<<K);++S)
{
for(int i=1;i<=n*m;++i)
{
for(int S0=S&(S-1);S0;S0=(S0-1)&S&(S-1))
if(f[i][S]>f[i][S0]+f[i][S^S0]-a[i])
{
f[i][S]=f[i][S0]+f[i][S^S0]-a[i];
pre[i][S]=make_pair(i,S0);
}
if(f[i][S]!=INF) q.push(make_pair(f[i][S],i));
}
Dijkstra(S);
}
printf("%d\n",f[p[1]][(1<<K)-1]);
dfs(p[1],(1<<K)-1);
for(int i=1;i<=n;++i,puts(""))
for(int j=1;j<=m;++j)
{
int u=pos(i,j);
if(!a[u]) putchar('x');
else if(ans[i][j]) putchar('o');
else putchar('_');
}
return 0;
}
P3264 [JLOI2015]管道連線
不同的是這道題的關鍵點分為幾類,求的是使得任意同類關鍵點都連通的最小花費。
先求一遍最小斯坦納樹,得到 \(f\) 陣列。
令 \(g(s)\) 表示令點類 \(s\) 連通的最小花費, \(cnl_i\) 表示類別為 \(i\) 的點集。則有轉移:
- 直接讓點類 \(s\) 中的每一類的所有點都連通,則 \(g(s)=\min_{i=1}^n f(i,\bigcup_{k \in s}cnl_k)\) 。
- 將 \(s\) 分裂成兩個集合,則 \(g(s)=\min_{t \subseteq s}g(t)+g(s-t)\) 。
這道題不知道為什麼用Dijkstra會比SPFA慢。
\(\text{Code}:\)
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <queue>
#define maxn 1005
#define maxm 3005
#define Rint register int
#define INF 0x3f3f3f3f
using namespace std;
typedef long long lxl;
template <typename T>
inline void read(T &x)
{
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
x*=f;
}
struct edge
{
int u,v,w,next;
}e[maxm<<1];
int head[maxn],k;
inline void add(int u,int v,int w)
{
e[k]=(edge){u,v,w,head[u]};
head[u]=k++;
}
int n,m,K,p[11];
int f[maxn][1<<11],g[1<<11],cnl[11];
bool vis[maxn];
typedef pair<int,int> pii;
priority_queue<pii,vector<pii>,greater<pii> > q;
inline void Dijkstra(int S)
{
memset(vis,0,sizeof(vis));
while(!q.empty())
{
int u=q.top().second;q.pop();
if(vis[u]) continue;
vis[u]=true;
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].v;
if(f[v][S]>f[u][S]+e[i].w)
{
f[v][S]=f[u][S]+e[i].w;
q.push(make_pair(f[v][S],v));
}
}
}
}
int main()
{
// freopen("P3264.in","r",stdin);
read(n),read(m),read(K);
memset(head,-1,sizeof(head));
for(int i=1,u,v,w;i<=m;++i)
{
read(u),read(v),read(w);
add(u,v,w);
add(v,u,w);
}
memset(f,0x3f,sizeof(f));
for(int i=1,c;i<=K;++i)
{
read(c);read(p[i]);
f[p[i]][1<<(i-1)]=0;
cnl[c]|=1<<(i-1);
}
for(int S=1;S<(1<<K);++S)
{
for(int i=1;i<=n;++i)
{
for(int S0=(S-1)&S;S0;S0=(S0-1)&(S-1)&S)
f[i][S]=min(f[i][S],f[i][S0]+f[i][S^S0]);
if(f[i][S]!=INF) q.push(make_pair(f[i][S],i));
}
Dijkstra(S);
}
memset(g,0x3f,sizeof(g));
g[0]=0;
for(int S=1;S<(1<<K);++S)
{
int S1=0;
for(int i=0;i<K;++i)
if((S>>i)&1) S1|=cnl[i+1];
for(int i=1;i<=n;++i)
g[S]=min(g[S],f[i][S1]);
for(int S0=(S-1)&S;S0;S0=(S0-1)&(S-1)&S)
g[S]=min(g[S],g[S0]+g[S^S0]);
}
printf("%d\n",g[(1<<K)-1]);
return 0;
}