【題解】G.Graph(2015-2016 ACM-ICPC, NEERC, Northern Subregional Contest)
阿新 • • 發佈:2020-11-02
題目連結G題
題意
序列 \(a_1,a_2,⋯,a_n\) 是一個排列, 當且僅當它含有 1 到 n 的所有整數。
排列 \(a_1,a_2,⋯,a_n\) 是一個有向圖的拓撲排序,當且僅當對於每條邊 \(u→v\),這個排列中 \(u\) 都出現在 \(v\) 之前。
給定一個有向無環圖,新增至多 k 條有向邊,使圖保持無環,且字典序最小的拓撲排序字典序最大
思路
很有意思的一道構造題。
主要想法就是字典序較大的連邊限制字典序小的。
兩個堆:一個小根一個大根(小根堆是當前可以填寫的節點集合,大根堆是已經分配了入邊的集合)
拓撲序中,要填x,如果不填x,將要填的後面一個更大,(意味著不填x更優)那麼將k條邊中一條分配給x,入大根堆。但是還有一些不能入的情況:
如果 k為0 或者 小根堆siz=1 且 大根堆空或大根堆最大小於x,那麼把x放入拓撲序,否則放到大根堆;
reason:k為0,沒有邊可以分配了
小根堆siz=1,是當前最後一個入度為0的點,如果加入邊會出現環
如果是當前最後一個入度為0的點且沒有點被加邊,那麼加邊就是浪費,不如直接拓撲
如果是當前最後一個入度為0的點且比所有被加邊的編號都要大,也是浪費(
如果小根堆空,那麼把大根堆top放入拓撲序並且和拓撲序前一個連入邊.
當一個點被放入拓撲序的時候,把所有指向的點入度--,如果為0,那麼入小根堆。
這樣就構成了一個系統:小根堆是可以放的點,大根堆是預備了一條邊,但是不知道pre是誰的點。顯然從任意一個點連邊都是合法的。那麼當不得不把這個點放進拓撲序的時候就選擇其中最大的節點pop就好了(在前面的儘可能大)
(不知道為啥這道題過不了樣例就AC……樣例奇怪得很)
(注:如果你WA on test1了,把檔案頭加上再試一遍))
Code
採用set替代堆。
#include <bits/stdc++.h> #define mp(x,y) make_pair(x,y) using namespace std; const int N=1e5+10; set<int>pmax,pmin; vector<pair<int,int> >edge; vector<int>g[N]; int in[N],ord[N],n,m,k,cnt,pre; void topo( int x ) { ord[++cnt]=x; pre=x; for ( int i=g[x].size()-1; ~i; i-- ) { int y=g[x][i]; if ( --in[y]==0 ) pmin.insert(y); } } int main() { freopen( "graph.in","r",stdin ); freopen( "graph.out","w",stdout ); scanf( "%d%d%d",&n,&m,&k ); for ( int i=1; i<=n; i++ ) g[i].clear(),in[i]=0; for ( int i=1,u,v; i<=m; i++ ) scanf( "%d%d",&u,&v ),g[u].push_back(v),in[v]++; pmin.clear(); pmax.clear(); edge.clear(); for ( int i=1; i<=n; i++ ) if ( in[i]==0 ) pmin.insert(i); pre=0; cnt=0; while ( cnt<n ) { if ( pmin.size()==0 ) //如果小根堆空,那麼把大根堆top放入拓撲序並且和拓撲序前一個連入邊 { int x=*--pmax.end(); edge.push_back( mp(pre,x) ); topo(x); pmax.erase(x); } else if ( !k || pmin.size()==1 && (pmax.size()==0 || *pmin.begin()>*--pmax.end()) ) { int x=*pmin.begin(); topo(x); pmin.erase(x); } //如果 k為0 或者 小根堆=1 且 大根堆空或大根堆最大小於x,那麼把x放入拓撲序 else //否則放入大根堆 { k--; int x=*pmin.begin(); pmin.erase(x); pmax.insert(x); } } for ( int i=1; i<=cnt; i++ ) printf( "%d ",ord[i] ); printf( "\n%d\n",edge.size() ); for ( int i=0; i<edge.size(); i++ ) printf( "%d %d\n",edge[i].first,edge[i].second ); }