zoukankan      html  css  js  c++  java
  • Steiner树

    Steiner树

    Steiner树大概就是指在一张无向有权图中给定若干个关键点,求出一个边权和最小的边集,使得在仅保留这个边集中的边的情况下,任意两个关键点相互之间连通。
    (当然如果是点权也没有问题,因为这个算法的扩展性很强?)
    就以下面这题为例讲一下求Steiner树吧。
    题目
    这就是求Steiner树的模板题,不过写起来因为坐标二维和数字相互转化的问题比较麻烦。
    然后求Steiner树大概是一个NP问题,我们只能设计一个状压来解决。
    (f_{i,S})表示以关键点(i)为根(或者说在包含关键点(i)的情况下),当前已经连通的关键点集合为(S)的最小代价。
    转移分为两种:
    (1.f_{i,S}=minlimits_{tsubset s}(f_{i,t}+f_{i,ssetminus t}-a_i))
    转移的意义非常显然就不说了,(-a_i)是因为(i)这个点会被重复算两次。
    如果是边权代价的话可以把(-a_i)去掉。
    (2.f_{i,S}=minlimits_{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
    ",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;
    }
    

    如果不需要具体方案的话我们有一个简单很多的写法。

    #include<bits/stdc++.h>
    using namespace std;
    int read(){int x;scanf("%d",&x);return x;}
    int min(int &a,int b){return b<a? a=b,1:0;}
    int amin(int a,int b){return a<b? a:b;}
    const int N=101,inf=0x3f3f3f3f,dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
    int n,m,k,a[N],f[N][1024],vis[N];queue<int>q;
    int move(int p,int k){int x=p/m+dx[k],y=p%m+dy[k],z=x*m+y;return (x<0||y<0||x==n||y==m)? -1:z;}
    void spfa(int s)
    {
        for(int i=0;i<n*m;++i) if(f[i][s]<inf) q.push(i),vis[i]=1;
        for(int u,v,k;!q.empty();) for(u=q.front(),q.pop(),vis[u]=k=0;k<4;++k) if(~(v=move(u,k))&&min(f[v][s],f[u][s]+a[v])&&!vis[v]) vis[v]=1,q.push(v);
    }
    int main()
    {
        memset(f,0x3f,sizeof f),n=read(),m=read(),k=0;int root;
        for(int i=0;i<n*m;++i) if(!(a[i]=read())) f[i][1<<(k++)]=0,root=i;
        for(int s=1,i,t;s<1<<k;spfa(s++)) for(i=0;i<n*m;++i) for(t=(s-1)&s;t;--t&=s) min(f[i][s],f[i][t]+f[i][s^t]-a[i]);
        printf("%d",f[root][(1<<k)-1]);
    }
    
  • 相关阅读:
    ES6 新特性
    基于.NET平台常用的框架整理
    你可能不知道的一些JavaScript 奇技淫巧
    js中apply方法的使用
    数据库中字段类型对应C#中的数据类型
    C# Socket SSL通讯笔记
    Media Types
    JS使用模板快速填充HTML控件数据 --- 自己写组件(0)
    FastDFS的配置、部署与API使用解读(8)FastDFS多种文件上传接口详解
    FastDFS的配置、部署与API使用解读(7)Nginx的FastDFS模块
  • 原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/12024441.html
Copyright © 2011-2022 走看看