礎石花冠 (corolla)
题目来源:\(2025\) 年省选集训第二轮 Day 1。
\(\S 1\quad\) 题意
给定长度为 \(n\) 的序列 \(a,b\)。定义 \(G=(V,E)\),其中 \(V=\set{1,2,\dots,n}\),\(E=\set{(u,v)\mid (u<v\land a_u<b_v) \lor (u>v\land b_u<a_v)}\)。
接下来,从 \(G\) 中删除了 \(m\) 条边,第 \(i\) 条边为 \((u_i,v_i)\)(方向任意)。试求边数最小的 \(G'\) 使得 \(G'\) 的传递闭包与 \(G\) 的传递闭包相同。
【数据范围】
对于所有数据,\(n \leq 10^5\),\(m \leq 10^5\),\({\color{red}0} \leq a_i, b_i \leq 2n\)。保证 \(|f(G)| \leq 4 \times 10^5\)。
| Subtask | \(n\) | \(m\) | 分值 | Subtask 依赖 |
|---|---|---|---|---|
| 1 | \(\leq 5\) | - | \(6\) | - |
| 2 | \(\leq 500\) | - | \(10\) | \(1\) |
| 3 | \(\leq 2500\) | \(m \leq 2500\) | \(18\) | - |
| 4 | \(\leq 10^5\) | \(m = 0\) | \(16\) | - |
| 5 | \(\leq 5 \times 10^4\) | \(m \leq 2500\) | \(18\) | \(3\) |
| 6 | \(\leq 5 \times 10^4\) | - | \(14\) | \(2,5\) |
| 7 | \(\leq 10^5\) | - | \(18\) | \(4,6\) |
\(\S 2\quad\) 思路
\(\S 2.1\quad\) \(O(n^3/w)\) 做法
肯定要缩点,因为一个强联通分量对应的边数最小的图就是 \(n\) 个点的简单环,于是我们将问题转化为一张有向无环图上(后文中的点均代指一个 SCC)。
接下来考虑何时 \(G\) 中的边 \((u,v)\) 不需要加入其中,当且仅当存在另一条路径从 \(u\) 指向 \(v\)。那么,注意到这条路径按照拓扑序在 \((u,v)\) 之前,即按照反拓扑序一次遍历每个点 \(u\),并加入 \(u\) 指出的边,那么这条路径一定会先加入到图中。
于是,只需要按反拓扑序将 \(u\) 的所有出边依次加入,如果某条边已经连通了,那么便不加入。
这里发现一个小问题,如果先加入蓝色的边就不对了。所以,在加边的时候也要按出边对应的点的拓扑序从小到大加入。
时间复杂度:\(O(n^3/w)\)。
\(\S 2.2\quad\) \(O(n\sqrt m)\) 做法
继续沿用 \(\S 2.1\) 的做法,考虑如何在大图上缩点。众所周知,常用的缩点方式有两种:
- \(\tt Tarjan\)
- \(\tt Kosaraju\)
而两者本质上都是 dfs,所以加速缩点和加速 dfs 并没有本质上的区别。不过 \(\tt Tarjan\) 存在两种转移,一种是访问没遍历过的边,另一种是还在栈中的点(这种很困难,可能需要单点修,矩形查?);而 \(\tt Kosaraju\) 就是在 dfs,所以只需要用线段树加速一下即可。而我们知道,缩点之后是能直接得到拓扑序的,所以接下来的问题转变为构造答案图。
到目前为止,均没有用到答案边数 \(\le 4\times 10^5\) 这条性质。所以,考虑我们在加边的时候,如何能尽量保证一定会加进去(因为这样总和不超过 \(4\times 10^5\))且不加进去的边数尽可能少。
考虑每次维护出当前已经连出的边对应的图,每次相当于加入一个新点放进去。实际上,不难发现只需要连接入度为 \(0\) 的点。除非不存在与某个入度为 \(0\) 的点的边,那么,在答案图上将该点和其所有出边删除(注意不是永久的删除,只是为了寻找点 \(u\) 的出边而临时删除的),这时候会多出一些新的入度为 \(0\) 的点,继续处理即可。
分析时间复杂度,与入度为 \(0\) 的点不存在边的次数至多 \(m\) 次,每次会遍历所有在 答案图 上的出边,而答案图上一个点的度数是 \(O(\sqrt m)\),因为任意两个点间都需要不存在边,这样就需要删除 \(O(\text{deg}(u)^2)\) 条边,而至多删除 \(m\) 条,得证。
时间复杂度:\(O(n\sqrt m)\)。
\(\S 3\quad\) 代码
#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
using i64 = long long;
using pii = pair<int, int>;
const int maxn = 200005;
int n, m, k;
int a[maxn], b[maxn];
map<pii, int> ban, sban;
int atr[maxn * 4], btr[maxn * 4], vis[maxn];
int ord[maxn], tot, din[maxn], sz[maxn];
std::vector<int> g[maxn];
void update(int tr[], int x, int d) {
x += k, tr[x] = d;
for (int i = x >> 1; i; i >>= 1)
tr[i] = max(tr[i << 1], tr[i << 1 | 1]);
}
int max_right(int tr[], int x, int v) {
x += k;
while (tr[x] <= v) {
if ((1 << __lg(x + 1)) == x + 1)
return n + 1;
if (x & 1) x ++;
x >>= 1;
}
while (x <= k) {
x <<= 1;
if (tr[x] <= v)
x ++;
}
return x - k;
}
int idx[maxn], scc;
void dfs(int u) {
vis[u] = 1, idx[u] = scc;
sz[scc] ++;
update(atr, n - u + 1, 0);
update(btr, u, 0);
int t = u;
while (t <= n) {
int v = max_right(btr, t, a[u]);
if (v > n) break;
if (!ban.count({u, v}))
dfs(v);
t = v + 1;
}
t = n - u + 1;
while (t <= n) {
int v = max_right(atr, t, b[u]);
if (v > n) break;
if (!ban.count({u, n - v + 1}))
dfs(n - v + 1);
t = v + 1;
}
if (!scc) ord[ ++ tot] = u;
}
std::vector<int> region[maxn];
int sub[maxn];
int main() {
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
cin >> n >> m, k = (1 << __lg(n) + 1) - 1;
for (int i = 1; i <= n; i ++)
cin >> a[i];
for (int i = 1; i <= n; i ++)
cin >> b[i];
for (int i = 1; i <= m; i ++) {
int u, v;
cin >> u >> v;
ban[{u, v}] = ban[{v, u}] = 1;
}
for (int i = 1; i <= n; i ++) {
atr[i + k] = a[i];
btr[i + k] = b[i];
}
reverse(atr + k + 1, atr + 1 + k + n);
for (int i = k; i >= 1; i --) {
atr[i] = max(atr[i << 1], atr[i << 1 | 1]);
btr[i] = max(btr[i << 1], btr[i << 1 | 1]);
}
for (int i = 1; i <= n; i ++)
if (!vis[i]) dfs(i);
for (int i = 1; i <= n; i ++) {
atr[i + k] = a[i] = 2 * n - a[i];
btr[i + k] = b[i] = 2 * n - b[i];
}
reverse(atr + k + 1, atr + 1 + k + n);
for (int i = k; i >= 1; i --) {
atr[i] = max(atr[i << 1], atr[i << 1 | 1]);
btr[i] = max(btr[i << 1], btr[i << 1 | 1]);
}
memset(vis, 0, sizeof vis);
for (int i = n; i >= 1; i --)
if (!vis[ord[i]])
scc ++, dfs(ord[i]);
for (auto [e, t] : ban)
sban[{idx[e.fi], idx[e.se]}] ++;
for (int i = 1; i <= n; i ++)
region[idx[i]].emplace_back(i);
queue<int> q;
std::vector<pii> res;
for (int i = scc; i >= 1; i -- ) {
if (region[i].size() > 1)
for (int j = 0; j < region[i].size(); j ++) {
int nxt = (j + 1) % region[i].size();
res.push_back({region[i][j], region[i][nxt]});
}
std::vector<int> add, del;
while (q.size()) {
int u = q.front();
q.pop();
if ((i64)sz[i] * sz[u] == sban[{i, u}]) { // 不存在 i -> u 的边
if (!din[u]) add.push_back(u);
for (auto v : g[u]) {
sub[v] ++, del.push_back(v);
if (din[v] - sub[v] == 0)
q.push(v);
}
} else {
din[u] ++, g[i].push_back(u);
res.push_back({region[i][0], region[u][0]});
}
}
q.push(i);
for (auto u : add) q.push(u);
for (auto u : del) sub[u] = 0;
}
cout << res.size() << '\n';
for (auto [u, v] : res)
cout << u << " " << v << '\n';
return 0;
}

浙公网安备 33010602011771号