一道好题
题目详见题目连接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
*/