CF125E MST Company【单度限制最小生成树】

题意:求最小生成树,与1连接的边有k条。输出连的边的编号


思路:我们先把除1外的点作最小生成树,再把1加进去(通过1把之前的集合连接起来)。加完之后,如果集合 大于 1,无解;如果新加的边 大于 k,无解;如果,从1出去的边的数量,比k还小,无解。

接下来就是,用从1出去的边1去替换那些第一次作最小生成树时候加的边。新加边,最优的情况是 新加边的权 减 去掉边的权 的差最小(增加值最小)。新加一条边,就会形成一个环,环中权最大的边就是加这条边进去最优。每添加一条边,都需要对环上的边一一枚举,太慢了。我们可以用dp记录下,在这个点加一条边,形成的环中最大边权的编号。每次一个点与1连接,就对它dfs,求dp。每次新加一条边,直到k。


也许讲的比较懵,参见 http://www.cnblogs.com/ylfdrib/archive/2010/08/21/1805505.html

部分引用论文:

第一步求解最小m度限制生成树:原图中去掉和V0相连的所有边,得到m个连通分量,而这m 个连通分量必须通过v0来连接,所以,在图G 的所有生成树中dT(v0)≥m。也就是说,当k<m时,问题无解。对每个连通分量求一次最小生成树,对于每个连通分量V’,求一点v1,v1∈V',且ω(v0,v1)=min{ω(v0,v')|v'∈V'},则该连通分量通过边(v1,v0)与v0相连。于是,我们就得到了一个m度限制生成树,不难证明,这就是最小m度限制生成树。

第二步,由最小m度限制生成树,得到最小m+1度限制生成树,对于和V0相邻的点v,则可以知道一定会有一个环出现,只要找到这个环上的最大权边,用边(V0, v)替换掉,就可以得到一个m+1度限制生成树,枚举所有和V0相邻点v,找到替换后增加权值最小的一次替换,就可以求得m+1度限制生成树。。如果每添加一条边,都需要对环上的边一一枚
举,时间复杂度将比较高,这里,动态规划就有了用武之地。设Best(v)为路径v0—v上与v0无关联且权值最大的边。定义father(v)为v的父结点,动态转移方程:Best(v)=max(Best(father(v)),ω(father(v),v)),边界条件为Best[v0]=-∞,Best[v’]=-∞| (v0,v’)∈E(T)。

第三步, 当度为K的时候就可以退出了。


#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<stdlib.h>
#include<math.h>
#include<vector>
#include<list>
#include<map>
#include<stack>
#include<queue>
#include<algorithm>
#include<numeric>
#include<functional>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int maxn = 5005; 
int pre[maxn],dp[maxn];
struct data
{
	int x,y,cost,id;
};

struct Edge
{
	int x,y,cost;
}edge[100005];

vector<data> st,p,en;
int vis[100005],ans[100005],tot;
int line[maxn][maxn],n;
int cmp(const data &a,const data &b)
{
	return a.cost < b.cost;
}
void init(int n)
{
	for(int i = 1; i <= n; i++)
		pre[i] = i;
	st.clear();
	en.clear();
	p.clear();
	memset(vis,0,sizeof vis);
	memset(line,0,sizeof line);
}

int fid(int x)
{
	if(pre[x] == x)
		return x;
	else
		return pre[x] = fid(pre[x]);
}

int dfs(int x,int pre,int id) //算环中边权最大id 
{
	if(edge[id].cost >= edge[ dp[pre] ].cost)
		dp[x] = id;	
	else
		dp[x] = dp[pre];
	for(int i = 1; i <= n; i++)
	{
		if(line[x][i] && i != pre)
			dfs(i,x,line[i][x]);
	}
}

int main(void)
{
	int m,k;
	while(scanf("%d%d%d",&n,&m,&k)!=EOF)
	{
		init(n);
		for(int i = 1; i <= m; i++) //st存与1连接的边 
		{
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			edge[i] = (Edge){a,b,c};
			if(a == 1)
				st.push_back((data){a,b,c,i});
			else if(b == 1)
				st.push_back((data){b,a,c,i});
			else
				p.push_back((data){a,b,c,i});
		}
		sort(p.begin(),p.end(),cmp);
		sort(st.begin(),st.end(),cmp);
		for(int i = 0; i < p.size(); i++)  //除1外,作最小生成树 
		{
			int x = p[i].x;
			int y = p[i].y;
			int nx = fid( x );
			int ny = fid( y );
			if(nx != ny)
			{
				pre[nx] = ny;
				line[x][y] = line[y][x] = p[i].id;
				vis[p[i].id] = 1;
			}
		}
		int num = 0;
		for(int i = 0; i < st.size(); i++) //通过1,连接它们 
 		{
			int x = st[i].x;
			int y = st[i].y;
			int nx = fid( x );
			int ny = fid( y );
			if(nx != ny)
			{
				pre[ny] = nx;
				dfs(y,0,0);
				vis[st[i].id] = 1;
				num++;
			}
		}
		int block = 0;
		for(int i = 1; i <= n; i++)
		{
			if(fid(i) == i)
				block++;
		}
		if(block > 1 || st.size() < k || num > k)
		{
			printf("-1\n");
			continue;
		}
		num = k - num;	
		while(num--) //替换边 
		{
			for(int i = 0; i < st.size(); i++)
			{
				if( !vis[ st[i].id ] )
				{
					int y = st[i].y;
					en.push_back((data){st[i].id,dp[y],
					st[i].cost-edge[ dp[y] ].cost,y});
					//add id,del id,增加量,新连的点
				}
			}
			sort(en.begin(),en.end(),cmp);
			vis[ en[0].x ] = 1;
			vis[ en[0].y ] = 0;
			line[ edge[en[0].y].x ][ edge[en[0].y].y ] = line[ edge[en[0].y].y ][ edge[en[0].y].x ] = 0;
			dfs(en[0].id,0,0);
			en.clear();
		}
		tot = 0;
		for(int i = 1; i <= m; i++)
		{
			if(vis[i])
				ans[tot++] = i;
		}
		printf("%d\n",tot);
		for(int i = 0 ; i < tot; i++)
			printf("%d%c",ans[i],i==tot-1?'\n':' ');
	}
	return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值