zoukankan      html  css  js  c++  java
  • 最小斯坦纳树初探

    问题描述

    斯坦纳树问题是组合优化学科中的一个问题。将指定点集合中的所有点连通,且边权总和最小的生成树称为最小斯坦纳树(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

  • 相关阅读:
    冒泡排序改
    冒泡排序 (基本类型),(引用类型)
    关于随机数列,对给定数目的自0开始步长为1的数字序列进行乱序。(可用作洗牌)
    Java基础之 移位操作
    保留代码,狼羊过河的问题(暂未理解)
    非计算机专业学习计算机
    CompletableFuture 捕获异常方式:handle、whenComplete、exceptionally
    nio socket聊天室
    java socket server接收并返回数据
    java socket套接字编程入门
  • 原文地址:https://www.cnblogs.com/nosta/p/10416451.html
Copyright © 2011-2022 走看看