Loading

礎石花冠 (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\) 的做法,考虑如何在大图上缩点。众所周知,常用的缩点方式有两种:

  1. \(\tt Tarjan\)
  2. \(\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;
}
posted @ 2025-05-11 11:29  Pigsyy  阅读(14)  评论(0)    收藏  举报