題意
個點, 條邊的有向圖,現可以往上添加 條邊,使得新圖最小拓撲序最大,此處的大與小均指序列的字典序。
思路
比賽的時候看到這道題,想的就是貪心。對於此類題目,一個很明顯的思路就是從開頭到末尾,依次貪心,即讓第一個位置字典序最大,再讓第二個最大,依次往下。
因此不難想到,對於某一拓撲層來說,我們需要對該層中最小節點加邊,使其在拓撲序之後的位置中出現。想到這一步之後,就陷入了卡頓,因爲不知道應該選哪個節點向最小的節點連邊。
看了其他人的做法之後,發現可以用一個最大堆存儲這些暫時不知道如何連邊的節點,在之後無法再繼續拓撲的時候,再從中選一個節點出來連邊。連邊的時候直接從當前拓撲序末尾節點連向該節點即可。
於是問題變成什麼樣的點我們不需要對其連邊。
- 沒有邊可以連了
- 當前拓撲序集合爲空,且最大堆中無節點,因此對當前節點加邊無意義
- 當前拓撲序集合爲空,且最大堆中最大的節點編號小於當前節點,因此對當前節點加邊無意義
總結
這是一道挺難想的利用拓撲序的構造題,對於此類腦洞大開的題的確沒有什麼太好的方法,只能多做多思考多總結了!
代碼
#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;
}