// 内部资料
传送门
考场上的思路
首先把所有度数为 的点提出来。发现让最大的连向其它的不如直接连成一条链,于是我就这么做了,结果大样例没过 QAQ。发现有可能让某个结点的后继结点去连边,这样会让答案更优。但是让谁去连边呢?QAQ 于是我就爆零了。
思路
这道题首先要发现一个重要的性质:对于每个点至多只有一条额外边连向它。
证明
假设有两条额外边连向它( 和 ),那么我们可以把连边方式改成链状( 和 )。显然这样的限制条件更强,因此不会有两条额外边连向同一个结点。
另外一个显然的结论是,如果我们想要让某个结点成为拓扑序的下一个元素,那么还没有出现在拓扑序的,编号比它小的结点的入度必须非零。换句话说,如果它们的度数为零,必须向它们连一条额外边。
由于要让字典序更大,根据贪心的思想,如果当前剩余额外边边数 大于等于当前度数为零的点的个数 -1,那么我们一定要让度数为 的编号最大的点成为下一个拓扑序中的元素。
现在考虑连边。由于之前证明了每个结点最多有一条边连向它,因此考虑一下如果一个当前入度为 的点已经有边连向它的情况。显然这条连向它的边已经在拓扑序里了,并且在其它条件不变的情况下,只要有边连向它,原拓扑序就不会被改变。因此我们不如重新连边,让当前度数为 的编号最大的点连向它,这样我们就能省下一条边。连边后,找到当前入度为 的编号最小的点作为拓扑序的下一个元素。如果之前有边连向它,那么那条边就不会被调整,就真的连向它了。
显然这么贪心是对的,也没有更优的方法了(因为我们在不断调整)。考虑实现。 显然如果按照上面说的让编号最大的结点连向其它结点的时间复杂度是 的(用一个集合去实现)(考虑一下原来没有任何边的情况)。由于前面说的连边实际上可以随便连(最后我们再来不断调整),只需要保证我们想要的那个成为下一个拓扑序中的元素即可,因此我们换种连边方式:我们把度数为 的点从小到大连边,连成一条链,这样做就可以过了。这是因为这样做出现刚连好边就删除的情况的总次数是 的(如果让最大的点连向其它点,每次连边都是 ,总共就是 ),因此时间复杂度为 。
参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
using LL = long long;
using ULL = unsigned long long;
using std::cin;
using std::cout;
using std::endl;
using INT_PUT = int;
INT_PUT readIn()
{
INT_PUT a = 0;
bool positive = true;
char ch = getchar();
while (!(std::isdigit(ch) || ch == '-')) ch = getchar();
if (ch == '-')
{
positive = false;
ch = getchar();
}
while (std::isdigit(ch))
{
(a *= 10) -= ch - '0';
ch = getchar();
}
return positive ? -a : a;
}
void printOut(INT_PUT x)
{
char buffer[20];
int length = 0;
if (x < 0) putchar('-');
else x = -x;
do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
do putchar(buffer[--length]); while (length);
}
const int maxn = int(1e5) + 5;
int n, m, k;
std::pair<int, int> origin[maxn];
using Graph = std::vector<std::vector<int>>;
Graph G;
int inDegree[maxn];
int from[maxn];
struct SegTree
{
};
void run()
{
n = readIn();
m = readIn();
k = readIn();
for (int i = 1; i <= m; i++)
{
origin[i].first = readIn();
origin[i].second = readIn();
}
std::sort(origin + 1, origin + 1 + m);
m = std::unique(origin + 1, origin + 1 + m) - (origin + 1);
G.resize(n + 1);
for (int i = 1; i <= m; i++)
G[origin[i].first].push_back(origin[i].second);
for (int i = 1; i <= m; i++)
inDegree[origin[i].second]++;
std::set<int> set;
for (int i = 1; i <= n; i++)
if (!inDegree[i])
set.insert(i);
int t = k;
std::vector<std::pair<int, int>> ans;
std::vector<int> topo;
while (set.size())
{
int vertex = *set.begin();
int c = 0;
for (auto it = set.begin(); it != set.end(); it++)
{
vertex = *it;
if (c == t && !from[*it])
break;
if (!from[*it]) c++;
}
for (auto it = set.begin(); *it != vertex; it = set.begin())
{
auto next = it;
next++;
G[*next].push_back(*it);
if (!from[*it]) t--;
from[*it] = *next;
inDegree[*it]++;
set.erase(it);
}
set.erase(set.begin());
if (from[vertex])
ans.push_back(std::make_pair(from[vertex], vertex));
topo.push_back(vertex);
for (int i = 0; i < G[vertex].size(); i++)
{
int to = G[vertex][i];
if (!(--inDegree[to]))
set.insert(to);
}
}
for (int v : topo)
{
printOut(v);
putchar(' ');
}
putchar('\n');
printOut(k - t);
putchar('\n');
for (auto e : ans)
{
printOut(e.first);
putchar(' ');
printOut(e.second);
putchar('\n');
}
}
int main()
{
#ifndef LOCAL
freopen("topo.in", "r", stdin);
freopen("topo.out", "w", stdout);
#endif
run();
return 0;
}
总结
构造题考虑调整法。