【2015-2016 ACM-ICPC, NEERC, Northern Subregional Contest G】【脑洞 拓扑排序 大根堆小根堆极限维护】Graph DAG加k边使得最小拓扑序最大

#include<stdio.h> 
#include<string.h>
#include<ctype.h>
#include<math.h>
#include<iostream>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;}
template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;}
const int N=1e5+10,M=0,Z=1e9+7,ms63=1061109567;
int casenum,casei;
set<int>done,now;
vector<int>a[N];
int ind[N],ord[N];
int n,m,g,k,x,y,pre;
vector<pair<int,int> >edge;
void topo(int x)
{
	ord[++g]=x;
	pre=x;
	for(int i=a[x].size()-1;~i;--i)
	{
		int y=a[x][i];
		if(--ind[y]==0)now.insert(y);
	}
}
void fre()
{
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
}
int main()
{
	fre();
	while(~scanf("%d%d%d",&n,&m,&k))
	{
		for(int i=1;i<=n;i++)
		{
			a[i].clear();
			ind[i]=0;
		}
		for(int i=1;i<=m;i++)
		{
			scanf("%d%d",&x,&y);
			a[x].push_back(y);
			++ind[y];
		}
		now.clear();done.clear();edge.clear();
		for(int i=1;i<=n;i++)if(ind[i]==0)now.insert(i);
		pre=0;
		g=0;while(g<n)
		{
			if(now.size()==0)//没有可以作为前驱的点,这时候就由我们自由安排了
			{
				int x=*--done.end();
				edge.push_back(MP(pre,x));
				topo(x);done.erase(x);
			}
			else if(!k||now.size()==1&&(done.size()==0||*now.begin()>*--done.end()))//没有边,无法改变拓扑序;会成环,不能改变拓扑序
			{
				int x=*now.begin();
				topo(x);now.erase(x);
			}
			else//有边,可以改变拓扑序
			{
				--k;int x=*now.begin();
				now.erase(x);done.insert(x);
			}
		}
		for(int i=1;i<=g;i++)printf("%d ",ord[i]);puts("");
		printf("%d\n",edge.size());
		for(int i=0;i<edge.size();i++)printf("%d %d\n",edge[i].first,edge[i].second);
	}
	return 0;
}
/*
【trick&&吐槽】
做构造题还是要开脑洞啊~
做构造题还是好玩呀~

【题意】
给你一个n个点m条边的有向无环图。
我们想要在其上最多添加k条边,使得——
1,这个图依然保持为有向无环图。
2,其最小字典序的拓扑排序结果尽可能大。

所谓"最小字典序的拓扑排序结果尽可能大",
是指我们加边之后形成了一个DAG,这个DAG上会有一个最小字典序的拓扑排序结果。
我们想使得加边操作尽可能好,从而使得这个最小字典序的拓扑排序结果尽可能大。

数据范围:1<=n,m,k<=1e5

【类型】
构造

【分析】
这道题要AC,还是需要很神奇又精妙的做法的。

首先,我们肯定要模拟拓扑排序。
具体的实现方法是,把所有入度为0的点放入一个小根堆,每次取出堆顶元素。(或者放入set,每次取出最小元素)。
然而,在这个时候我们是可以做加边操作的。
目前是要出堆顶元素,然而这样我们的字典序就为最小的了。
对于字典序这种东西,如果想要使其尽可能大,则是可以从前向后做贪心操作的。
这个不仅满足为当前单步最优,同时还会使得全局最优。所以可以直接贪心。
于是我们当前就必须要在这个节点之前加一条边,使得这个当前的最小节点排得尽可能靠后。

但是,什么时候不能加这条边呢?
1,如果当前这个点是当前最后一个入度为0的点,如果把它也加上一条前导边,会导致出现环。
2,如果当前这个点是当前最后一个入度为0的点,而且没有点是已经被加边的,那这个点如果加边,我们就是浪费了一条边,不如直接拓扑出。
3,如果当前这个点是当前最后一个入度为0的点,而且它比当前所有已经被加边的点的编号要大,那加边也是在浪费边,也不如直接拓扑出。
4,没有边的时候……
其他情况下,我们把点前面加边。

我们把已经加边的点不直接输出。而是扔进一个大根堆里。
因为这些点之间并没有前后效应,即,我们出任意一个点都是可以的。
所以我们扔进大根堆里,当不得不出的时候,我们一定从大根堆里,选取编号最大的节点拓扑出去。
所以,如果当前没有节点入度为0怎么办?很显然,从大根堆里直接拓扑。

而拓扑意味着:
1,编号
2,记录其为前驱
3,释放所有是其后继的点

这样子——
1,最节省边数
2,每次把字典序小的节点向后藏

于是拓扑序尽可能大。

【时间复杂度&&优化】
O(nlogn)

*/

    原文作者:拓扑排序
    原文地址: https://blog.csdn.net/snowy_smile/article/details/49852263
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞