P8375 [APIO2022] 游戏
O((n + m)k)
(下面内容主要参考自 dengyaotriangle 在LOJ上的提交记录)
维护
但是注意:对
我们对
一开始,所有的特殊点区间都对应叶子,所有的非特殊点区间都对应根节点。
我们声称:每个区间都只需调整到线段树的某个节点。
假设当前连了一条边
线段树的两个区间只有三种位置关系:相离、包含和重合(我们单独考虑重合)。
下面的推导中,我们只用承认两个基本事实:
- 只要可以判断是否有环,我们就不用进行下一步的调整。
- 如果
u \rightarrow v 没有在u 和v 的区间上带来改变,我们可以直接返回。
Case 1 u 和 v 的区间相离
分为两种情况:
假如
否则
Case 2 u 和 v 的区间重合
这种情况不会带来环,我们可以直接返回。
当然,这里假设了
Case 3 u 和 v 的区间相互包含
如果
此时
如果
此时
注意可能产生长度为 1 的区间,需要直接返回。
如果不进行这两步调整,我们就无法在这一步以及以后的过程中判断是否有环,所以它们是必要的。
其它两种情况,可以证明都不会产生环。比如,
这种情况下一定不会产生环,只需考虑和
于是复杂度
代码:
#include <bits/stdc++.h>
#include "game.h"
#define For(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define rFor(i, b, a) for (int i = b, i##end = a; i >= i##end; --i)
const int kN = 3e5 + 5;
int n, k, lb[kN], rb[kN];
std::vector<int> G[kN][2];
void init(int _n, int _k) {
n = _n, k = _k;
std::fill(lb, lb + n, -1);
std::fill(rb, rb + n, k);
For(i, 0, k - 1) {
lb[i] = rb[i] = i;
}
}
bool add(int, int);
bool add(int u) {
for (int v : G[u][0]) if (add(u, v)) return true;
for (int v : G[u][1]) if (add(v, u)) return true;
return false;
}
bool add(int u, int v) {
if (u < k && v < k) return u >= v;
if (rb[u] < lb[v]) return false;
if (lb[u] > rb[v]) return true;
if (lb[u] == lb[v] && rb[u] == rb[v]) return false;
int midu = (lb[u] + rb[u]) >> 1, midv = (lb[v] + rb[v]) >> 1;
if (lb[v] >= lb[u] && rb[v] <= midu) {
rb[u] = midu;
return lb[u] == rb[u] || add(u);
}
if (rb[u] <= rb[v] && lb[u] > midv) {
lb[v] = midv + 1;
return lb[v] == rb[v] || add(v);
}
return false;
}
int add_teleporter(int u, int v) {
G[u][0].push_back(v), G[v][1].push_back(u);
return add(u, v);
}
根据WeLikeStudying的题解,应该存在一种跟二进制分组有关的做法。
这是比较自然的:这个问题很容易用离线分治解决,而二进制分组的名头就在于它可以在线地解决一类 cdq 分治能够解决的问题。
但以前见过的例子都是可以分体贡献的,这个好像不太一样。而且我没看懂他写的东西/kk。并不知道二进制分组怎么扩展到更一般的问题。