【题解】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就好了(在前面的尽可能大)

(注:如果你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 );
}
posted @ 2020-11-02 12:42  MontesquieuE  阅读(84)  评论(0编辑  收藏  举报