【2015-2016 NEERC - G】Graph【構造 + 拓撲排序】

題意

nn 個點,mm 條邊的有向圖,現可以往上添加 kk 條邊,使得新圖最小拓撲序最大,此處的大與小均指序列的字典序。(1n100000,0m,k100000)(1\leq n\leq 100000,0\leq m,k\leq 100000)


思路

比賽的時候看到這道題,想的就是貪心。對於此類題目,一個很明顯的思路就是從開頭到末尾,依次貪心,即讓第一個位置字典序最大,再讓第二個最大,依次往下。

因此不難想到,對於某一拓撲層來說,我們需要對該層中最小節點加邊,使其在拓撲序之後的位置中出現。想到這一步之後,就陷入了卡頓,因爲不知道應該選哪個節點向最小的節點連邊。

看了其他人的做法之後,發現可以用一個最大堆存儲這些暫時不知道如何連邊的節點,在之後無法再繼續拓撲的時候,再從中選一個節點出來連邊。連邊的時候直接從當前拓撲序末尾節點連向該節點即可。

於是問題變成什麼樣的點我們不需要對其連邊。

  1. 沒有邊可以連了
  2. 當前拓撲序集合爲空,且最大堆中無節點,因此對當前節點加邊無意義
  3. 當前拓撲序集合爲空,且最大堆中最大的節點編號小於當前節點,因此對當前節點加邊無意義

總結

這是一道挺難想的利用拓撲序的構造題,對於此類腦洞大開的題的確沒有什麼太好的方法,只能多做多思考多總結了!


代碼

#include <bits/stdc++.h>
#define rep(i,a,b) for(int i = a; i <= b; i++)
const int N = 1e5+100;
using namespace std;

int n,m,k,deg[N],ans[N],tot;
vector<pair<int,int> > Edge;
vector<int> G[N];
priority_queue<int> q1, q2; // q1-拓撲,q2-之後加邊的點

void init(){
	scanf("%d%d%d",&n,&m,&k);
	rep(i,1,m){
		int u,v; scanf("%d%d",&u,&v);
		G[u].push_back(v); deg[v]++;
	}
	rep(i,1,n)
		if(!deg[i]) q1.push(-i);
}

void update(int x){
	for(auto it:G[x])
		if((--deg[it]) == 0) q1.push(-it);
	ans[++tot] = x;
}

void output(){
	rep(i,1,n) printf("%d%c", ans[i], " \n"[i==n]);
	printf("%d\n", (int)Edge.size());
	for(auto it:Edge)
		printf("%d %d\n", it.first, it.second);
}

int main()
{
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	init();
	while(q1.size() || q2.size()){
		if(q1.size()){
			int x = -q1.top(); q1.pop();
			if(k == 0 || (!q1.size() && (!q2.size() || x > q2.top()))) update(x);
			else k--, q2.push(x);
		}
		else if(q2.size()){
			int x = q2.top(); q2.pop();
			Edge.push_back(make_pair(ans[tot],x));
			update(x);
		}
	}
	output();
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章