业界萌新对斯坦纳树的小结

本文深入探讨斯坦纳树问题,一种组合优化问题,旨在寻找使关键点间开销最小的最短网络。介绍斯坦纳树的基本概念,求解方法,包括状态定义、转移方程,以及如何通过状态压缩优化算法效率。附带详细代码示例和应用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

业界萌新对斯坦纳树的小结

0.简介

斯坦纳树问题是组合优化问题,与最小生成树相似,是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点,使生成的最短网络开销最小。

                                                                                                                                                                           ——百度百科

简单来讲,斯坦纳树问题一般就是给定一个n个点m条边的带非负权无向图,其中有k个关键点,要求选取一个子图,让所有关键点联通,而最小斯坦纳树则在斯坦纳树的前提下,让总费用最小

在此推荐一位学长的文章,写得很不错:详解斯坦纳点及斯坦纳树及模版归纳总结

 

1.最小斯坦纳树的求解方法

斯坦纳树只要求k个关键点联通,显然不能够直接用最小生成树的方法解决。

  • 结论:最优解的方案一定会选取一棵树。
  • 证明:可行解显然是一个连通图,而如果图上存在一个环,费用为C。我们可以将其中最长的一条边去掉,使得新费用C'=C-len,由于len\in \mathbb{Z}^{+},所以C'<=C。因此只会选取一棵树。

这样选取一棵树求最优解的问题,常见的思路是DP

 

2.具体实现

2.1状态  

DP[i][state]   表示以i为根,关键点的选取状态为 state 的最优费用。

2.2转移

两重转移方程:

第一重转移,更新新的选取状态:f[i][state]=min(f[i][s1]+f[i][s2]) , s1,s2\subseteq state

第二重转移,松弛新的选取状态:f[i][state]=min(f[j][state]+len[j][i])

由于之后的选取状态会由第一重转移更新,所以只需要对当前的选取状态进行松弛即可。

第一重转移直接DP,第二重转移用SPFA松弛

(PS:一般出题人不会在此处卡SPFA,但如果路遇毒瘤出题人黑心卡SPFA,还是用dijstra吧)。

时间复杂度显然:O(3^kn+c2^kE),c是SPFA的常数,E是边数。

2.3细节与技巧:

 

  • 倘若每一次都SPFA全图松弛会产生大量冗余运算,SPFA只需要对当前层节点进行松弛。
  • 子集枚举时可以用:for\;(s=S\;and\;(S-1);s;s=(s-1)\;and\;S)   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

显然的最小斯坦纳树问题,f[i][j][state]  表示以i,j这个格子为根,关键点选取状态为state的最小个数。

#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.萌新的总结

用状压DP解决最小斯坦纳树的时间复杂度对于k是指数级别的,所以一般的最小斯坦纳数问题中,k的范围在10左右。

斯坦纳树实现并不难,思维难度也不高,还是一个很实用的算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值