【2015-2016 ACM-ICPC, NEERC, Northern Subregional Contest G】【脑洞 拓扑排序 大根堆小根堆极限维护】Graph DAG加k边使得最小拓扑序最大

来源:互联网 发布:阿里云短信api 编辑:程序博客网 时间:2024/04/29 07:27
#include<stdio.h> #include<string.h>#include<ctype.h>#include<math.h>#include<iostream>#include<string>#include<set>#include<map>#include<vector>#include<queue>#include<bitset>#include<algorithm>#include<time.h>using namespace std;#define MS(x,y) memset(x,y,sizeof(x))#define MC(x,y) memcpy(x,y,sizeof(x))#define MP(x,y) make_pair(x,y)#define ls o<<1#define rs o<<1|1typedef long long LL;typedef unsigned long long UL;typedef unsigned int UI;template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;}template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;}const int N=1e5+10,M=0,Z=1e9+7,ms63=1061109567;int casenum,casei;set<int>done,now;vector<int>a[N];int ind[N],ord[N];int n,m,g,k,x,y,pre;vector<pair<int,int> >edge;void topo(int x){ord[++g]=x;pre=x;for(int i=a[x].size()-1;~i;--i){int y=a[x][i];if(--ind[y]==0)now.insert(y);}}void fre(){    freopen("graph.in","r",stdin);    freopen("graph.out","w",stdout);}int main(){fre();while(~scanf("%d%d%d",&n,&m,&k)){for(int i=1;i<=n;i++){a[i].clear();ind[i]=0;}for(int i=1;i<=m;i++){scanf("%d%d",&x,&y);a[x].push_back(y);++ind[y];}now.clear();done.clear();edge.clear();for(int i=1;i<=n;i++)if(ind[i]==0)now.insert(i);pre=0;g=0;while(g<n){if(now.size()==0)//没有可以作为前驱的点,这时候就由我们自由安排了{int x=*--done.end();edge.push_back(MP(pre,x));topo(x);done.erase(x);}else if(!k||now.size()==1&&(done.size()==0||*now.begin()>*--done.end()))//没有边,无法改变拓扑序;会成环,不能改变拓扑序{int x=*now.begin();topo(x);now.erase(x);}else//有边,可以改变拓扑序{--k;int x=*now.begin();now.erase(x);done.insert(x);}}for(int i=1;i<=g;i++)printf("%d ",ord[i]);puts("");printf("%d\n",edge.size());for(int i=0;i<edge.size();i++)printf("%d %d\n",edge[i].first,edge[i].second);}return 0;}/*【trick&&吐槽】做构造题还是要开脑洞啊~做构造题还是好玩呀~【题意】给你一个n个点m条边的有向无环图。我们想要在其上最多添加k条边,使得——1,这个图依然保持为有向无环图。2,其最小字典序的拓扑排序结果尽可能大。所谓"最小字典序的拓扑排序结果尽可能大",是指我们加边之后形成了一个DAG,这个DAG上会有一个最小字典序的拓扑排序结果。我们想使得加边操作尽可能好,从而使得这个最小字典序的拓扑排序结果尽可能大。数据范围:1<=n,m,k<=1e5【类型】构造【分析】这道题要AC,还是需要很神奇又精妙的做法的。首先,我们肯定要模拟拓扑排序。具体的实现方法是,把所有入度为0的点放入一个小根堆,每次取出堆顶元素。(或者放入set,每次取出最小元素)。然而,在这个时候我们是可以做加边操作的。目前是要出堆顶元素,然而这样我们的字典序就为最小的了。对于字典序这种东西,如果想要使其尽可能大,则是可以从前向后做贪心操作的。这个不仅满足为当前单步最优,同时还会使得全局最优。所以可以直接贪心。于是我们当前就必须要在这个节点之前加一条边,使得这个当前的最小节点排得尽可能靠后。但是,什么时候不能加这条边呢?1,如果当前这个点是当前最后一个入度为0的点,如果把它也加上一条前导边,会导致出现环。2,如果当前这个点是当前最后一个入度为0的点,而且没有点是已经被加边的,那这个点如果加边,我们就是浪费了一条边,不如直接拓扑出。3,如果当前这个点是当前最后一个入度为0的点,而且它比当前所有已经被加边的点的编号要大,那加边也是在浪费边,也不如直接拓扑出。4,没有边的时候……其他情况下,我们把点前面加边。我们把已经加边的点不直接输出。而是扔进一个大根堆里。因为这些点之间并没有前后效应,即,我们出任意一个点都是可以的。所以我们扔进大根堆里,当不得不出的时候,我们一定从大根堆里,选取编号最大的节点拓扑出去。所以,如果当前没有节点入度为0怎么办?很显然,从大根堆里直接拓扑。而拓扑意味着:1,编号2,记录其为前驱3,释放所有是其后继的点这样子——1,最节省边数2,每次把字典序小的节点向后藏于是拓扑序尽可能大。【时间复杂度&&优化】O(nlogn)*/

1 0
原创粉丝点击