1. 程式人生 > 實用技巧 >【題解】G.Graph(2015-2016 ACM-ICPC, NEERC, Northern Subregional Contest)

【題解】G.Graph(2015-2016 ACM-ICPC, NEERC, Northern Subregional Contest)

題目連結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 );
}