斯坦纳树

删除回忆录丶 提交于 2019-12-11 18:58:25

斯坦纳树

斯坦纳树大概就是指在一张无向有权图中给定若干个关键点,求出一个边权和最小的边集,使得在仅保留这个边集中的边的情况下,任意两个关键点相互之间连通。
(当然如果是点权也没有问题,因为这个算法得扩展性很强?)
就以下面这题为例讲一下求斯坦纳树吧。
题目
这就是求斯坦纳树的模板题,不过写起来因为坐标二维和数字相互转化的问题比较麻烦。
然后求斯坦纳树大概是一个NP问题,我们只能设计一个状压来解决。
\(f_{i,S}\)表示以关键点\(i\)为根(或者说在包含关键点\(i\)的情况下),当前已经连通的关键点集合为\(S\)的最小代价。
转移分为两种:
\(1.f_{i,S}=\min\limits_{t\subset s}(f_{i,t}+f_{i,s\setminus t}-a_i)\)
转移的意义非常显然就不说了,\(-a_i\)是因为\(i\)这个点会被重复算两次。
如果是边权代价的话可以把\(-a_i\)去掉。
\(2.f_{i,S}=\min\limits_{\exists (i,j)\in E}(f_{j,S}+a_i)\)
这个类似于Bellman-Ford中的松弛操作。
如果是边权代价的话把\(a_i\)换成\(w(e_{i,j})\)
然而转移顺序比较麻烦,我们需要制定一个符合拓扑序的转移顺序。
一种可行的方案是先枚举集合\(S\),再枚举根\(i\),然后枚举子集完成第一类转移,再用队列优化的Bellman-Ford完成第二类转移。
这样的复杂度应该是\(O(|V|3^{|S|}+|V||E|2^{|S|})\)

#include<bits/stdc++.h>
#define pi pair<int,int>
#define fi first
#define se second
using namespace std;
const int inf=0x3f3f3f3f,dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int n,m,k,root,f[101][1024],a[101],ans[101],vis[101];
pi pre[101][1024];
queue<int>q;
int read(){int x;scanf("%d",&x);return x;}
int trans(pi a){return a.fi*m+a.se;}
pi trans(int a){return pi(a/m,a%m);}
int in(pi a){return a.fi>=0&&a.se>=0&&a.fi<n&&a.se<m;}
pi operator+(pi a,pi b){return pi(a.fi+b.fi,a.se+b.se);}
void spfa(int s)
{
    while(!q.empty())
    {
    int u=q.front();q.pop(),vis[u]=0;
    for(int i=0;i<4;++i)
    {
        if(!in(trans(u)+pi(dx[i],dy[i]))) continue;
        int v=trans(trans(u)+pi(dx[i],dy[i]));
        if(f[v][s]<=f[u][s]+a[v]) continue;
        f[v][s]=f[u][s]+a[v],pre[v][s]={u,s};
        if(!vis[v]) vis[v]=1,q.push(v);
    }
    }
}
void dfs(int u,int s)
{
    if(!pre[u][s].se) return ;
    ans[u]=1;
    if(pre[u][s].fi==u) dfs(u,s^pre[u][s].se);
    dfs(pre[u][s].fi,pre[u][s].se);
}
int main()
{
    memset(f,0x3f,sizeof f),n=read(),m=read(),k=0;
    for(int i=0;i<n*m;++i) if(!(a[i]=read())) f[i][1<<(k++)]=0,root=i;
    for(int s=1,t,i,x;s<1<<k;++s)
    {
    for(i=0;i<n*m;++i)
    {
        for(t=s&(s-1);t;t=(t-1)&s) if(f[i][s]>(x=f[i][t]+f[i][s^t]-a[i])) f[i][s]=x,pre[i][s]={i,t};
        if(f[i][s]<inf) q.push(i),vis[i]=1;
    }
    spfa(s);
    }
    printf("%d\n",f[root][(1<<k)-1]),dfs(root,(1<<k)-1);
    for(int i=0,x=0,j;i<n;++i,puts("")) for(j=0;j<m;++j) if(!a[x]) ++x,putchar('x'); else putchar(ans[x]? 'o':'_'),++x;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!