[Cnoi2019]须臾幻境(LCT 维护最大生成树)
这曾今有一个凄婉哀伤的故事,但是被出题人删档弄丢了。
题目名字这么高尚,背景你却给我咕了??
Description
点数 \(n\) ,边数 \(m\) ,询问 \(q\) 次,求一段区间内的边组成的残图的连通块个数。
\(n,\ q \leq 10 ^ 5,\ m \leq 2 \cdot 10 ^ 5\)
Analysis
首先我们要知道对于一个残图,它的点数减生成森林的边数就是连通块的个数。
因为它本身是作为图为存在的,所以即需要维护区间生成树(森林)。
Solution
又是区间,又是生成树什么跟图扯上关系的要求,看起来就头大。
可以考虑用 \(\text{LCT}\) 维护生成树。
但是生成树可能会有很多种,我们就需要所有边编号尽可能靠近那个区间。
怎么办。
我们考虑在加边的时候,假如当前两个点原先已经联通,我们尝试去找连通块上最早加进来的边,并把他删掉。
这样就能保证在图的连通性不变的情况下,让生成树上的边尽可能靠近右边界。
这样我们可以在 \(\text{LCT}\) 里面维护连通块内编号最小的地方。
众所周知 \(\text{LCT}\) 是维护的点权,所以要把边化点。不过比较典的套路,可以直接把边也当作点塞到 \(\text{LCT}\) 里面,就像 \(\text{kruskal}\) 重构树那样连边。
所这样只需要把边的编号当作点权,其他图上的点设成 \(\infty\) ,维护最小值就行了。
那如果我们想要随时知道前 r 条边有多少条生成树边,把这些甩到主席树上就行了。但是注意这玩意相当于能查询前 r 条边有多少条存在于生成树上。
因为我们是尽量让所有树边编号尽可能大,所以就类似二位数点 \(f_r - f_{l - 1}\) 那样做个差就知道前 \(r\) 条边最多有多少条在区间 \([l,\ r]\) 内。
不得不说 \(\text{LCT}\) 有些操作看起来是真的厉害(
时间复杂度 \(O(n \log n)\) 。
Code
Code
/*
*/
#include
using namespace std;
#define File(a) freopen(a".in", "r", stdin), freopen(a".out", "w", stdout);
#define Check(a) freopen(a".in", "r", stdin), freopen(a".ans", "w", stdout);
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair pii;
#define fi first
#define se second
#define mp std::make_pair
const int mod = 998244353;
template
inline int M(A x) {return x;}
template
inline int M(A x, B ... args) {return 1ll * x * M(args...) % mod;}
#define mi(x) (x >= mod) && (x -= mod)
#define ad(x) (x < 0) && (x += mod)
const int N = 2e5 + 10, P = 3e5 + 10, INF = 1e9;
int n, m, q, op, rt[N], del[N];
struct edge {int u, v;} e[N];
struct LCT {
#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
int ch[P][2], fa[P], mn[P], va[P], rev[P], st[P], tp;
#define pd(x) (!(rs(fa[x]) ^ x))
#define isrt(x) (ls(fa[x]) ^ x && rs(fa[x]) ^ x)
inline void pushup(int x) {mn[x] = min(va[x], min(mn[ls(x)], mn[rs(x)]));}
#define prev(x) (rev[x] ^= 1, ls(x) ^= rs(x) ^= ls(x) ^= rs(x))
inline void pushdown(int x) {
rev[x] && (
ls(x) && (prev(ls(x))),
rs(x) && (prev(rs(x))), rev[x] = 0
);
}
inline void rotate(int x) {
int d = fa[x], t = pd(x);
ch[d][t] = ch[x][t ^ 1]; fa[ch[x][t ^ 1]] = d;
!isrt(d) && (ch[fa[d]][pd(d)] = x);
fa[x] = fa[d]; ch[x][t ^ 1] = d; fa[d] = x;
pushup(d); pushup(x);
}
inline void push(int x) {
for (; !isrt(x); x = fa[x]) st[++tp] = x;
st[++tp] = x;
while (tp) pushdown(st[tp--]);
}
inline void splay(int x) {
for (push(x); !isrt(x); rotate(x)) {
!isrt(fa[x]) && (rotate((pd(x) ^ pd(fa[x])) ? x : fa[x]), 1);
}
pushup(x);
}
inline void access(int x) {
for (int y = 0; x; y = x, x = fa[x]) splay(x), rs(x) = y, pushup(x);
}
inline void makert(int x) {access(x); splay(x); prev(x);}
inline void split(int x, int y) {makert(x); access(y); splay(y);}
inline int firt(int x) {
access(x); splay(x);
for (; ls(x); pushdown(x), x = ls(x));
splay(x); return x;
}
inline void link(int x, int y) {makert(x); fa[x] = y;}
inline void cut(int x, int y) {split(x, y); ls(y) = fa[x] = 0; pushup(y);}
#undef ls
#undef rs
#undef pd
#undef isrt
} L;
struct PresiTree {
struct mdzz {int su, ch[2];} tr[N << 5];
int cnt;
#define ls(x) tr[x].ch[0]
#define rs(x) tr[x].ch[1]
inline void modify(int &x, int l, int r, int k, int p) {
x = ++cnt; tr[x] = tr[p]; ++tr[x].su;
if (l == r) return ;
int mid = (l + r) >> 1;
if (k <= mid) modify(ls(x), l, mid, k, ls(p));
else modify(rs(x), mid + 1, r, k, rs(p));
}
inline int query(int x, int l, int r, int L, int R) {
if (!x) return 0;
if (L <= l && r <= R) return tr[x].su;
int mid = (l + r) >> 1, ret = 0;
if (L <= mid) ret += query(ls(x), l, mid, L, R);
if (R > mid) ret += query(rs(x), mid + 1, r, L, R);
return ret;
}
#undef ls
#undef rs
} Pt;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m >> q >> op;
for (int i = 0; i <= n; ++i) L.va[i] = L.mn[i] = INF;
for (int i = 1; i <= m; ++i) L.va[i + n] = L.mn[i + n] = i;
for (int i = 1, j; i <= m; ++i) {
std::cin >> e[i].u >> e[i].v;
int x = e[i].u, y = e[i].v;
if (!(x ^ y)) {del[i] = i; continue;}
L.firt(x) == L.firt(y) && (
L.split(x, y), j = L.mn[y],
L.cut(e[j].u, j + n), L.cut(e[j].v, j + n), del[j] = i
);
L.link(x, i + n); L.link(y, i + n);
}
for (int i = 1; i <= m; ++i) {
if (!del[i]) del[i] = m + 1;
Pt.modify(rt[i], 1, m + 1, del[i], rt[i - 1]);
}
int ans = 0;
while (q--) {
int l, r; std::cin >> l >> r;
op && (l = (l + ans) % m + 1, r = (r + ans) % m + 1);
l > r && (l ^= r ^= l ^= r);
int a = Pt.query(rt[r], 1, m + 1, r + 1, m + 1);
int b = Pt.query(rt[l - 1], 1, m + 1, r + 1, m + 1);
ans = n - a + b;
std::cout << ans << "\n";
}
return 0;
}