Problem G. Graph 2015-2016 acmicpc neerc 拓扑排序模拟

一道好题

题目详见题目连接G graph

显然模拟拓扑排序的步骤是必不可少了。

假设我们当前有t个点,他们的入度均为0.我们不知道该选取哪一个。

我们把这t个点按从小到大排好序(放入小顶堆),假设我们目前有k条边(k < t),我们贪心的把前k小的元素都加上一条边,这样的话,我们要选的就是第k+1个点,可以保证这样选取是全局最优的。

现在问题来了,我们给前k个点加一条边,保证了这k个点不被取到,但是,我们怎么知道这条边的父节点是谁呢?没错,我们确实不知道,但是我们目前还不需要知道,所以我们打上标记,可以把他们放入一个大顶堆里面去。大顶堆里面的元素表示入度为1,但父节点尚不清楚的点。


所以每次选取的元素必然与小顶堆及大顶堆有关。

选取时,若小顶堆的大小为size,我们要尽可能挑出大的来,就要使小顶堆里的元素个数尽可能的少,所以我们尽可能用多的边加到小顶堆里的元素上,并把这个元素移动到大顶堆里面去,直到小顶堆里面只剩一个元素。如果边数不够的话,就从小顶堆里面选取最小的元素。

如果边数够的话,小顶堆里还剩一个元素,把这个元素和大顶堆里面最大的元素作比较,如果小顶堆的元素大,那么选取小顶堆里的元素,否则给小顶堆的元素加边,扔到大顶堆里面去,并且从大顶堆里面选取出一个最大的来。


代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+7;
vector<int> G[maxn];
int n,m,k;
int ind[maxn];
set<int> sma,big;
typedef set<int>::iterator itp;
int cnt;
pair<int,int> ans[maxn*10];
int main(){
	freopen("graph.in","r",stdin);  
    freopen("graph.out","w",stdout);  
    
	scanf("%d%d%d",&n,&m,&k);
	for(int i = 0;i < m;++i) {
		int u,v;
		scanf("%d%d",&u,&v);
		G[u].push_back(v);
		ind[v]++;
	}
	for(int i = 1;i <= n;++i) if(!ind[i]) sma.insert(i);
	int last = 0;
	while(sma.size() || big.size()){
		int rt;itp it;int edges,pre;
		if(sma.size() == 0){
			goto mark;
		}
		it = sma.begin();
		edges = min(k,(int)sma.size()-1);
		pre = 0;
		for(int i = 0;i < edges;++i){
			pre = *it;
			itp nit = it;
			nit++;
			sma.erase(it);
			big.insert(pre);
			it = nit;
		}
		
		k -= edges;
		if(k && big.size() && *big.rbegin() > *sma.begin()){
			k -= 1;
			big.insert(*sma.begin());
			sma.clear();
			mark:
			rt = *big.rbegin();
			big.erase(big.find(*big.rbegin()));
			ans[cnt++] = make_pair(last,rt);
		}
		else{
			rt = *sma.begin();
			sma.erase(sma.begin());
		}
		
		for(int i = 0;i < G[rt].size();++i){
			--ind[G[rt][i]];
			if(ind[G[rt][i]] == 0) 
				sma.insert(G[rt][i]);
		}
		printf("%d ",rt);
		last = rt;
	}
	printf("\n%d\n",cnt);
	for(int i = 0;i < cnt;++i){
		printf("%d %d\n",ans[i].first,ans[i].second);
	}
	return 0;
}
/*
7 4 1
2 1
4 3
4 6
6 7

*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值