zoukankan      html  css  js  c++  java
  • 【题解】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( "
    %d
    ",edge.size() );
            for ( int i=0; i<edge.size(); i++ )
                    printf( "%d %d
    ",edge[i].first,edge[i].second );
    }
    
  • 相关阅读:
    python3 5个带key内置函数
    python3常用内置函数总结
    python入门基础-函数装饰器的理解
    python入门基础-三元表达式、命名空间、作用域、函数名本质、闭包
    python入门基础-初识函数
    第二章001编写脚本
    Appium 自动化测试第一天——基础知识
    python基础-读取文件
    linux6.4 安装python3 pip setuptools
    selenium+python之HTML测试报告
  • 原文地址:https://www.cnblogs.com/UntitledCpp/p/13913836.html
Copyright © 2011-2022 走看看