一道好题
题目详见题目连接G graph
显然模拟拓扑排序的步骤是必不可少了。
假设我们当前有t个点,他们的入度均为0.我们不知道该选取哪一个。
我们把这t个点按从小到大排好序(放入小顶堆),假设我们目前有k条边(k < t),我们贪心的把前k小的元素都加上一条边,这样的话,我们要选的就是第k+1个点,可以保证这样选取是全局最优的。
现在问题来了,我们给前k个点加一条边,保证了这k个点不被取到,但是,我们怎么知道这条边的父节点是谁呢?没错,我们确实不知道,但是我们目前还不需要知道,所以我们打上标记,可以把他们放入一个大顶堆里面去。大顶堆里面的元素表示入度为1,但父节点尚不清楚的点。
所以每次选取的元素必然与小顶堆及大顶堆有关。
选取时,若小顶堆的大小为size,我们要尽可能挑出大的来,就要使小顶堆里的元素个数尽可能的少,所以我们尽可能用多的边加到小顶堆里的元素上,并把这个元素移动到大顶堆里面去,直到小顶堆里面只剩一个元素。如果边数不够的话,就从小顶堆里面选取最小的元素。
如果边数够的话,小顶堆里还剩一个元素,把这个元素和大顶堆里面最大的元素作比较,如果小顶堆的元素大,那么选取小顶堆里的元素,否则给小顶堆的元素加边,扔到大顶堆里面去,并且从大顶堆里面选取出一个最大的来。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+7;
vector<int> G[maxn];
int n,m,k;
int ind[maxn];
set<int> sma,big;
typedef set<int>::iterator itp;
int cnt;
pair<int,int> ans[maxn*10];
int main(){freopen("graph.in","r",stdin); freopen("graph.out","w",stdout); scanf("%d%d%d",&n,&m,&k);for(int i = 0;i < m;++i) {int u,v;scanf("%d%d",&u,&v);G[u].push_back(v);ind[v]++;}for(int i = 1;i <= n;++i) if(!ind[i]) sma.insert(i);int last = 0;while(sma.size() || big.size()){int rt;itp it;int edges,pre;if(sma.size() == 0){goto mark;}it = sma.begin();edges = min(k,(int)sma.size()-1);pre = 0;for(int i = 0;i < edges;++i){pre = *it;itp nit = it;nit++;sma.erase(it);big.insert(pre);it = nit;}k -= edges;if(k && big.size() && *big.rbegin() > *sma.begin()){k -= 1;big.insert(*sma.begin());sma.clear();mark:rt = *big.rbegin();big.erase(big.find(*big.rbegin()));ans[cnt++] = make_pair(last,rt);}else{rt = *sma.begin();sma.erase(sma.begin());}for(int i = 0;i < G[rt].size();++i){--ind[G[rt][i]];if(ind[G[rt][i]] == 0) sma.insert(G[rt][i]);}printf("%d ",rt);last = rt;}printf("\n%d\n",cnt);for(int i = 0;i < cnt;++i){printf("%d %d\n",ans[i].first,ans[i].second);}return 0;
}
/*
7 4 1
2 1
4 3
4 6
6 7*/