传送门:https://codeforces.com/gym/100801
题意:
给你一个DAG图,你最多可以进行k次操作,每次操作可以连一条有向边,问你经过连边操作后最小拓扑序的最大值是多少
题解:
最小拓扑序:与普通拓扑序不同的是,用一个小根堆记录入度为0的点做拓扑排序即可
怎么样使得最小拓扑序最大呢?已知拓扑序是入度小的点在前面,那么,如果我们可以使得大的点的度数尽量小或者是小的点度数尽量大就可以使得拓扑序变大了,由于我们只有加边的操作,那么我们可以将边尽量从大的点往小的点去连边
我们定义小根堆q以便于得到最小拓扑序
为了使得最小拓扑序最大,我们这里有一个贪心的过程
贪心的正确性证明:如果当前操作使得当前字典序最大,那么所有操作后字典序一定最大
我们把已经加边的点不直接输出。而是扔进一个大根堆里。
因为这些点之间并没有前后效应,即,我们出任意一个点都是可以的。
所以我们扔进大根堆里,当不得不出的时候,我们一定从大根堆里,选取编号最大的节点拓扑出去
所以,如果当前没有节点入度为0怎么办?很显然,从大根堆里直接拓扑。
每次取出小根堆中的点时,我们都将小根堆中的点往大根堆里面放,如果小根堆为空时,我们就需要对大根堆中的点进行连边操作了,取出大根堆中的点,进行拓扑排序的正常操作记录答案即可;
代码:
/**
* ┏┓ ┏┓
* ┏┛┗━━━━━━━┛┗━━━┓
* ┃ ┃
* ┃ ━ ┃
* ┃ > < ┃
* ┃ ┃
* ┃... ⌒ ... ┃
* ┃ ┃
* ┗━┓ ┏━┛
* ┃ ┃ Code is far away from bug with the animal protecting
* ┃ ┃ 神兽保佑,代码无bug
* ┃ ┃
* ┃ ┃
* ┃ ┃
* ┃ ┃
* ┃ ┗━━━┓
* ┃ ┣┓
* ┃ ┏┛
* ┗┓┓┏━┳┓┏┛
* ┃┫┫ ┃┫┫
* ┗┻┛ ┗┻┛
*/
// warm heart, wagging tail,and a smile just for you!
//
// _ooOoo_
// o8888888o
// 88" . "88
// (| -_- |)
// O = /O
// ____/`---'\____
// .' | |// `.
// / ||| : |||//
// / _||||| -:- |||||-
// | | \ - /// | |
// | \_| ''---/'' | |
// .-\__ `-` ___/-. /
// ___`. .' /--.-- `. . __
// ."" '< `.___\_<|>_/___.' >'"".
// | | : `- \`.;` _ /`;.`/ - ` : | |
// `-. \_ __ /__ _/ .-` / /
// ======`-.____`-.___\_____/___.-`____.-'======
// `=---='
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// 佛祖保佑 永无BUG
#include <set>
#include <map>
#include <deque>
#include <queue>
#include <stack>
#include <cmath>
#include <ctime>
#include <bitset>
#include <cstdio>
#include <string>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef pair<LL, LL> pLL;
typedef pair<LL, int> pLi;
typedef pair<int, LL> pil;;
typedef pair<int, int> pii;
typedef unsigned long long uLL;
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define bug printf("*********
")
#define FIN freopen("input.txt","r",stdin);
#define FON freopen("output.txt","w+",stdout);
#define IO ios::sync_with_stdio(false),cin.tie(0)
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]
"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]
"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<<z<<"]
"
const double eps = 1e-8;
const int mod = 1e9 + 7;
const int maxn = 3e5 + 5;
const int INF = 0x3f3f3f3f;
const LL INFLL = 0x3f3f3f3f3f3f3f3f;
struct EDGE {
int v, nxt;
} edge[maxn << 1];
int head[maxn], tot;
void add_edge(int u, int v) {
edge[tot].v = v;
edge[tot].nxt = head[u];
head[u] = tot++;
}
int degree[maxn];
vector<int> G[maxn];
priority_queue<int, vector<int>, greater<int> >q; //小根堆放现在的点
priority_queue<int> p;//大根堆放要连的点
int ans[maxn];
int num, used;
int a[maxn], b[maxn];
void solve(int u) {
//排序排序
ans[++num] = u;
for(int i = 0; i < G[u].size(); i++) if(!--degree[G[u][i]]) q.push(G[u][i]);
if(q.empty()) {
if(!p.empty()) {
int v = p.top(); p.pop();
//大往小连边
G[u].push_back(v);
//记录加的边
a[++used] = u; b[used] = v;
solve(v);
}
}
}
int main() {
#ifndef ONLINE_JUDGE
FIN
#endif
// freopen("graph.in", "r", stdin);
// freopen("graph.out", "w+", stdout);
int n, m, k;
scanf("%d%d%d", &n, &m, &k);
memset(head, -1, sizeof(head));
memset(degree, 0, sizeof(degree));
tot = 0;
for(int i = 1, u, v; i <= m; i++) {
scanf("%d%d", &u, &v);
add_edge(u, v);
G[u].push_back(v);
degree[v]++;
}
for(int i = 1; i <= n; i++) {
if(degree[i] == 0) q.push(i);
}
while(!q.empty()) {
//小根堆不为空或者大根堆不为空并且小根堆堆顶的元素小于大根堆堆顶的元素
if(k && (q.size() > 1 || (!p.empty() && q.top() < p.top()))) {
k--;
int u = q.top();
q.pop();
p.push(u);
//如果入度为0的点用完了,就开始连边
if(q.empty()) {
int v = p.top(); p.pop();
G[ans[num]].push_back(v);
//记录加的边
a[++used] = ans[num]; b[used] = v;
solve(v);
}
} else {
//如果当前点是最后一个入度为0的点,那么加边是没有意义的,直接拓扑
//如果当前这个点是当前最后一个入度为0的点,而且没有点是已经被加边的,那这个点如果加边,我们就是浪费了一条边,不如直接拓扑出
//如果当前这个点是当前最后一个入度为0的点,而且它比当前所有已经被加边的点的编号要大,那加边也是在浪费边,也不如直接拓扑出
int v = q.top(); q.pop();
solve(v);
}
}
for(int i = 1; i <= n; i++) {
printf("%d%c", ans[i], i == n ? '
' : ' ');
}
printf("%d
", used);
for(int i = 1; i <= used; i++) {
printf("%d %d
", a[i], b[i]);
}
return 0;
}