問題描述
斯坦納樹問題是組合優化學科中的一個問題。將指定點集合中的所有點連通,且邊權總和最小的生成樹稱為最小斯坦納樹(Minimal Steiner Tree),其實最小生成樹是最小斯坦納樹的一種特殊情況。而斯坦納樹可以理解為使得指定集合中的點連通的樹,但不一定最小。(by Angel_Kitty)
解決方案
似乎沒有多項式演算法。在資料範圍允許時可使用dp來解。具體地,設(F[i,s])為根在點(i),樹中包含的指定集合點的集合為(s)時的最優解。兩種轉移
[
f[i,s]=min_{tsubset s} f[i,t]+f[i`,s-t]+e[i`,i]\
f[i,s]=min f[i`,s]+e[i`,i]
]
顯然第二種轉移需要迭代/最短路演算法。由於存在第二種轉移,假設轉移時能保證(f[*,t])已經被處理好,第一種轉移轉移可以進一步簡化為
[
f[i,s]=min_{tsubset s} f[i,t]+f[i,s-t]
]
這樣做就大功告成了。
練習題
bzoj2595 [WC2008]遊覽計劃
最小化點權和,大致相同,設(F[i,j,s])為根在點((i,j)),指定集合狀態為(s)的最小點權和,轉移有
[
f[i,j,s]=min f[i`,j`,s]+c[i,j]\
f[i,j,s]=min_{tsubset s} f[i,j,t]+f[i`,j`s-t]=min_{tsubset s} f[i,j,t]+f[i,j,s-t]-c[i,j]
]
#include <bits/stdc++.h>
using namespace std;
const int N=11;
const int inf=0x3f3f3f3f;
const int dx[]={0,0,-1,1};
const int dy[]={-1,1,0,0};
int n,m,K,st[N][N];
int vis[N][N],a[N][N],f[N][N][1<<N],pre[N][N][1<<N];
std::queue<int> Q;
bool inq[N*N];
int ecd(int i,int j) {return i*10+j;}
int ecd(int i,int j,int s) {return i*100000+j*10000+s;}
void dcd(int c,int&i,int&j) {i=c/10,j=c%10;}
void dcd(int c,int&i,int&j,int&s) {i=c/100000,j=(c/10000)%10,s=c%10000;}
bool upd(int i,int j,int s,int p,int q,int t,int w) {
if(f[i][j][s]>w) {
f[i][j][s]=w;
pre[i][j][s]=ecd(p,q,t);
return 1;
}
return 0;
}
void spfa(int s) {
int x,y,i,j,tmp;
while(!Q.empty()) {
int x=Q.front();
Q.pop();
inq[x]=0;
dcd(x,i,j);
for(int k=0; k<4; ++k) {
x=i+dx[k],y=j+dy[k];
if(x<0||x>=n||y<0||y>=m) continue;
if(upd(x,y,s,i,j,s,f[i][j][s]+a[x][y])&&!inq[tmp=ecd(x,y)]) {
Q.push(tmp),inq[tmp]=1;
}
}
}
}
void dfs(int i,int j,int s) {
int p,q,t;
vis[i][j]=1;
if(!pre[i][j][s]) return;
dcd(pre[i][j][s],p,q,t);
dfs(p,q,t);
if(p==i&&q==j) dfs(p,q,s^t);
}
int main() {
memset(f,inf,sizeof f);
scanf("%d%d",&n,&m);
for(int i=0; i<n; ++i)
for(int j=0; j<m; ++j) {
scanf("%d",&a[i][j]);
if(!a[i][j]) {
st[i][j]=1<<(K++);
f[i][j][st[i][j]]=0;
}
}
int fll=1<<K,tmp;
for(int s=1; s<fll; ++s) {
for(int i=0; i<n; ++i)
for(int j=0; j<m; ++j) {
for(int t=s&(s-1); t; t=(t-1)&s)
upd(i,j,s, i,j,t, f[i][j][t]+f[i][j][s^t]-a[i][j]);
if(f[i][j][s]!=inf) {
Q.push(tmp=ecd(i,j));
inq[tmp]=1;
}
}
spfa(s);
}
for(int i=0; i<n; ++i)
for(int j=0; j<m; ++j) if(!a[i][j]) {
printf("%d
",f[i][j][fll-1]);
dfs(i,j,fll-1);
for(int p=0; p<n; ++p,puts(""))
for(int q=0; q<m; ++q) {
if(!a[p][q]) putchar(`x`);
else if(vis[p][q]) putchar(`o`);
else putchar(`_`);
}
return 0;
}
}
bzoj4006 [JLOI2015]管道連線
需要處理出同種頻道的最小斯坦納樹,在進行dp組合得到最優解。設(f[i,s])為根在(i)包含關鍵點集為(s)的最小邊權和,(dp[s])表示包含頻道集為(s)的最小邊權和,(ki[i])表示頻道(i)包含的關鍵點集,轉移有
[
dp[s]=min_{i=1}^n f[i,cup_{kin s}ki[k]]\
dp[s]=min_{tsubset s} dp[t]+dp[s-t]
]
#include <bits/stdc++.h>
using namespace std;
const int N=1003;
const int inf=0x3f3f3f3f;
int n,m,p;
int head[N],to[N<<3],len[N<<3],last[N<<3];
int ki[N],f[N][1<<10],dp[1<<10];
void add_edge(int x,int y,int w) {
static int cnt=1;
to[cnt]=y;
len[cnt]=w;
last[cnt]=head[x];
head[x]=cnt++;
}
queue<int> Q;
bool inq[N];
void spfa(int s) {
for(int i=1; i<=n; ++i) {
inq[i]=1; Q.push(i);
}
while(!Q.empty()) {
int x=Q.front(); Q.pop();
inq[x]=0;
for(int i=head[x]; i; i=last[i]) {
if(f[to[i]][s]>f[x][s]+len[i]) {
f[to[i]][s]=f[x][s]+len[i];
if(!inq[to[i]]) {
inq[to[i]]=1;
Q.push(to[i]);
}
}
}
}
}
int main() {
scanf("%d%d%d",&n,&m,&p);
for(int i=1,x,y,w; i<=m; ++i) {
scanf("%d%d%d",&x,&y,&w);
add_edge(x,y,w);
add_edge(y,x,w);
}
memset(f,inf,sizeof f);
for(int i=1,x,y; i<=p; ++i) {
scanf("%d%d",&x,&y);
ki[x]|=1<<(i-1);
f[y][1<<(i-1)]=0;
}
for(int s=1; s<(1<<p); ++s) {
for(int i=1; i<=n; ++i)
for(int t=s&(s-1); t; t=(t-1)&s) {
f[i][s]=min(f[i][s],f[i][t]+f[i][s^t]);
}
spfa(s);
}
memset(dp,inf,sizeof dp);
dp[0]=0;
for(int s=1; s<(1<<p); ++s) {
int k=0;
for(int i=0; i<p; ++i) if((s>>i)&1) k|=ki[i+1];
for(int i=0; i<n; ++i) dp[s]=min(dp[s],f[i][k]);
for(int t=s&(s-1); t; t=(t-1)&s) dp[s]=min(dp[s]s,dp[t]+dp[s^t]);
}
printf("%d
",dp[(1<<p)-1]);
return 0;
}
bzoj4774 修路
hdu4085 Peach Blossom Spring
zoj3613 Wormhole Transport