zoukankan      html  css  js  c++  java
  • 【斯坦纳树】【LA5717】Beijing 2011 Peach Blossom Spring解题报告

    在不务正业大半年后继续开始写正经的解题报告……


    题目链接:

    https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3718


    题意:

    一个N<=50,M<=1000的边带权无向图,给定K<=5,第1~K号点是住宅,第N-K+1~N号点是避难所。要求选出一些边,把它们连起来,使得每个住宅都能走到一个避难所(注意一个避难所只能给一个住宅用),求最小的权值之和。


    斯坦纳树:

    这道题是基于斯坦纳树的。斯坦纳树的定义是:给定一张图和一个顶点集合,要求选出一些边,使得集合中的顶点连通。显然最小生成树是斯坦纳树的一个特殊情况:集合就是整个顶点集合V。

    可以用状压DP求解斯坦纳树:令f[i][s]=(以i为根,集合中顶点连通情况至少为s的最小代价)。

    首先从小到大枚举s转移。有两种转移方式:

    1. f[i][s]=min(f[i][s], f[i][t]+f[i][s-t]),要求t是s的子集。枚举i和t即可完成。 枚举t可以用:for(t=s;t;t=(t-1)&s)。这个转移的意思是,将以i为根的两棵树“拼”起来。

    2. f[i][s]=min(f[i][s], f[j][s]+w(i,j)),要求i,j之间有连边。这个意思是从j这个根往外“长”。

    第二种转移比较麻烦,因为没有一个确定的顺序。怎么办呢?SPFA。在第一种转移结束后,建超级源S,向每个点连边w(S,i)=f[i][s],原图中的边保持不变。然后从超级源S开始做一次最短路,就可以把第二种转移搞定了。


    题解:

    我们的这道题基本就是斯坦纳树。但还有一小点不同:最后不一定是所有的住宅&避难所都在同一个联通块中。例如:住宅1,2和避难所8,9连通,住宅3和避难所10连通,这也是符合基本法的,可以作为一组解。

    怎么办呢?在求完斯坦纳树之后,额外再进行一次“子集合并”的DP,注意,这里所有的合法状态都必须是住宅数=避难所数的。


    代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<queue>
    using namespace std;
    const int INF=0x7fffffff/2;
    const int SIZEN=60;
    int N,M,K;
    vector<pair<int,int> > c[SIZEN];
    void SPFA(int S,int dis[]){
    	static bool inq[SIZEN];
    	static queue<int> Q;
    	for(int i=0;i<=N;i++) inq[i]=false,dis[i]=INF;
    	while(!Q.empty()) Q.pop();
    	dis[S]=0;Q.push(S);inq[S]=true;
    	while(!Q.empty()){
    		int x=Q.front();Q.pop();inq[x]=false;
    		for(int i=0;i<c[x].size();i++){
    			int u=c[x][i].first,w=c[x][i].second;
    			if(dis[x]+w<dis[u]){
    				dis[u]=dis[x]+w;
    				if(!inq[u]){
    					inq[u]=true;
    					Q.push(u);
    				}
    			}
    		}
    	}
    	//for(int i=1;i<=N;i++) cout<<dis[i]<<" ";cout<<endl;
    }
    int F[SIZEN][1<<10]={0};//i为根,连通状态至少为s
    int dis[SIZEN]={0};
    void Steiner(void){
    	for(int i=0;i<=N;i++){
    		for(int j=0;j<(1<<(2*K));j++) F[i][j]=INF;
    	}
    	for(int i=1;i<=K;i++){
    		F[i][1<<(i-1)]=0;
    		F[N+1-i][1<<(i+K-1)]=0;
    	}
    	for(int s=0;s<(1<<(2*K));s++){
    		for(int i=1;i<=N;i++){
    			for(int t=s;t;t=(t-1)&s){
    				F[i][s]=min(F[i][s],F[i][t]+F[i][s-t]);
    			}
    		}
    		for(int i=1;i<=N;i++) c[0][i-1].second=F[i][s];
    		SPFA(0,dis);
    		for(int i=1;i<=N;i++) F[i][s]=min(F[i][s],dis[i]);
    	}
    }
    bool is_balanced(int s){
    	int x=0;
    	for(int i=0;i<K;i++){
    		if(s&(1<<i)) x++;
    		if(s&(1<<(K+i))) x--;
    	}
    	return x==0;
    }
    int dp[1<<10];
    void Final_DP(void){
    	for(int i=0;i<(1<<(2*K));i++) dp[i]=INF;
    	for(int s=0;s<(1<<(2*K));s++){
    		if(!is_balanced(s)) continue;
    		for(int i=1;i<=N;i++) dp[s]=min(dp[s],F[i][s]);
    		for(int t=s;t;t=(t-1)&s){
    			dp[s]=min(dp[s],dp[t]+dp[s-t]);
    		}
    	}
    	int ans=dp[(1<<(2*K))-1];
    	if(ans==INF) printf("No solution
    ");
    	else printf("%d
    ",ans);
    }
    void read(void){
    	scanf("%d%d%d",&N,&M,&K);
    	for(int i=0;i<=N;i++) c[i].clear();
    	for(int i=1;i<=M;i++){
    		int a,b,w;
    		scanf("%d%d%d",&a,&b,&w);
    		c[a].push_back(make_pair(b,w));
    		c[b].push_back(make_pair(a,w));
    	}
    	for(int i=1;i<=N;i++){
    		c[0].push_back(make_pair(i,0));
    	}
    }
    int main(void){
    	//freopen("input.in","r",stdin);
    	int kase;
    	scanf("%d",&kase);
    	while(kase--){
    		read();
    		Steiner();
    		Final_DP();
    	}
    	return 0;
    }
    

    由于忘掉“No Solution”,WA掉一次……
  • 相关阅读:
    asp.net(c#)网页跳转七种方法小结
    asp.net用Zxing库实现条形码输出的具体实现
    SQL中 patindex函数的用法
    escape()、encodeURI()、encodeURIComponent()区别详解
    sql语句分页代码
    memcache安装
    LVS和Haproxy机器必须注意的几个参数
    Redis 三主三从集群搭建
    mogodb安装步骤及注意事项
    系统故障等级和故障报告规定
  • 原文地址:https://www.cnblogs.com/wmdcstdio/p/7554218.html
Copyright © 2011-2022 走看看