业界萌新对斯坦纳树的小结
0.简介
斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。
——百度百科
简单来讲,斯坦纳树问题一般就是给定一个n个点m条边的带非负权无向图,其中有k个关键点,要求选取一个子图,让所有关键点联通,而最小斯坦纳树则在斯坦纳树的前提下,让总费用最小。
在此推荐一位学长的文章,写得很不错:详解斯坦纳点及斯坦纳树及模版归纳总结。
1.最小斯坦纳树的求解方法
斯坦纳树只要求k个关键点联通,显然不能够直接用最小生成树的方法解决。
- 结论:最优解的方案一定会选取一棵树。
- 证明:可行解显然是一个连通图,而如果图上存在一个环,费用为。我们可以将其中最长的一条边去掉,使得新费用,由于,所以。因此只会选取一棵树。
这样选取一棵树求最优解的问题,常见的思路是。
2.具体实现
2.1状态
表示以为根,关键点的选取状态为 的最优费用。
2.2转移
两重转移方程:
第一重转移,更新新的选取状态: ,
第二重转移,松弛新的选取状态:
由于之后的选取状态会由第一重转移更新,所以只需要对当前的选取状态进行松弛即可。
第一重转移直接,第二重转移用松弛
(PS:一般出题人不会在此处卡,但如果路遇毒瘤出题人黑心卡,还是用吧)。
时间复杂度显然:,c是的常数,E是边数。
2.3细节与技巧:
- 倘若每一次都全图松弛会产生大量冗余运算,只需要对当前层节点进行松弛。
- 子集枚举时可以用: and表示位运算的与。
3.一个例题:[WC2008][luoguP4294]游览计划
题目描述
从未来过绍兴的小D有幸参加了Winter Camp 2008,他被这座历史名城的秀丽风景所吸引,强烈要求游览绍兴及其周边的所有景点。
主办者将绍兴划分为N行M列(N×M)个分块,如下图(8×8):
景点含于方块内,且一个方块至多有一个景点。无景点的方块视为路。
为了保证安全与便利,主办方依据路况和治安状况,在非景点的一些方块内安排不同数量的志愿者;在景点内聘请导游(导游不是志愿者)。在选择旅游方案时,保证任意两个景点之间,存在一条路径,在这条路径所经过的每一个方块都有志愿者或者该方块为景点。既能满足选手们游览的需要,又能够让志愿者的总数最少。
例如,在上面的例子中,在每个没有景点的方块中填入一个数字,表示控制该方块最少需要的志愿者数目:
图中用深色标出的方块区域就是一种可行的志愿者安排方案,一共需要20名志愿者。由图可见,两个相邻的景点是直接(有景点内的路)连通的(如沈园和八字桥)。
现在,希望你能够帮助主办方找到一种最好的安排方案。
输入输出格式
输入格式:
第一行有两个整数,N和M,描述方块的数目。
接下来N行,每行有M个非负整数,如果该整数为0,则该方块为一个景点;
否则表示控制该方块至少需要的志愿者数目。相邻的整数用(若干个)空格隔开,
行首行末也可能有多余的空格。
输出格式:
由N+1行组成。第一行为一个整数,表示你所给出的方案中安排的志愿者总数目。
接下来N行,每行M个字符,描述方案中相应方块的情况:
'_'(下划线)表示该方块没有安排志愿者;
'o'(小写英文字母o)表示该方块安排了志愿者;
'x'(小写英文字母x)表示该方块是一个景点;
注:请注意输出格式要求,如果缺少某一行或者某一行的字符数目和要求不一致(任何一行中,多余的空格都不允许出现),都可能导致该测试点不得分。
输入输出样例
输入样例#1:
4 4
0 1 1 0
2 5 5 1
1 5 5 1
0 1 1 0
输出样例#1:
6
xoox
___o
___o
xoox
说明
所有的 10 组数据中 N, M ,以及景点数 K 的范围规定如下
输入文件中的所有整数均不小于 0 且不超过 2^16
感谢@panda_2134 提供Special Judge
Solution
显然的最小斯坦纳树问题, 表示以这个格子为根,关键点选取状态为的最小个数。
#include<bits/stdc++.h>
using namespace std;
const int MAXN=12;
const int MAXS=2005;
const int dx[4]={0,0,-1,1};
const int dy[4]={1,-1,0,0};
const int INF=0x3f3f3f3f;
int n,m,cnt,xt,yt;
int f[MAXN][MAXN][MAXS],c[MAXN][MAXN],p[MAXN][MAXN];
struct node{int x,y,s; } pre[MAXN][MAXN][MAXS];
bool ans[MAXN][MAXN],vis[MAXN][MAXN][MAXS];
queue<node> Q;
inline int read()
{int f=1,x=0; char c=getchar();while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+c-'0'; c=getchar(); }return x*f;
}
void find(int x,int y,int s)
{ans[x][y]=1;node q=pre[x][y][s];if (q.x==0) return;find(q.x,q.y,q.s);if (q.x==x&&q.y==y) find(x,y,(s-q.s)|p[x][y]);
}
void spfa()
{while (!Q.empty()){node q=Q.front(); Q.pop();int x=q.x,y=q.y,s=q.s;vis[x][y][s]=0;for (int i=0;i<4;i++){int xx=x+dx[i],yy=y+dy[i],ss=s|p[xx][yy];if (xx<1||xx>n||yy<1||yy>m) continue;if (f[x][y][s]+c[xx][yy]<f[xx][yy][ss]) {f[xx][yy][ss]=f[x][y][s]+c[xx][yy];pre[xx][yy][ss]=(node){x,y,s};if (s==ss&&(!vis[xx][yy][ss])) vis[xx][yy][ss]=1,Q.push((node){xx,yy,ss});}}}
}
int main()
{n=read(),m=read(),cnt=0;memset(f,INF,sizeof f);for (int i=1;i<=n;i++)for (int j=1;j<=m;j++){c[i][j]=read();if (!c[i][j]) p[i][j]=(1<<(cnt++)),xt=i,yt=j,f[i][j][p[i][j]]=0; }for (int S=1;S<(1<<cnt);S++){for (int i=1;i<=n;i++)for (int j=1;j<=m;j++)if ((S&p[i][j]) || !p[i][j]){for (int s=(S-1)&S;s;s=(s-1)&S){int t=f[i][j][ (S-s)|p[i][j] ]+f[i][j][ s|p[i][j] ]-c[i][j]; //(i,j)这一点重复计算了,把c[i][j]消去。 if (f[i][j][S]>t) f[i][j][S]=t,pre[i][j][S]=(node){i,j,s|p[i][j]};//更新f[i][j][S],并记录路径。 }if (f[i][j][S]<INF) vis[i][j][S]=1,Q.push((node){i,j,S}); }spfa();}printf("%d\n",f[xt][yt][(1<<cnt)-1]);find(xt,yt,(1<<cnt)-1);for (int i=1;i<=n;i++){for (int j=1;j<=m;j++)if (p[i][j]) putchar('x');else if (ans[i][j]) putchar('o');else putchar('_');puts("");}return 0;
}
4.一些斯坦纳树题
[THUSC2017]巧克力 斯坦纳树+随机+二分
[BZOJ 4006] 管道连接
[hdu 3331]Trip the Lights Fantastic
[HDU 4085] Peach Blossom Spring
[ZOJ 3613] Wormhole Transport
5.萌新的总结
用状压解决最小斯坦纳树的时间复杂度对于是指数级别的,所以一般的最小斯坦纳数问题中,的范围在左右。
斯坦纳树实现并不难,思维难度也不高,还是一个很实用的算法。