#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|1 typedef 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) */