J - Avengers: Infinite War
题意:
n
n
个节点个操作,操作分四种:
- 1 x y: 1 x y : 在 (x,y) ( x , y ) 节点连接一条边
- 2 x y: 2 x y : 断开 (x,y) ( x , y ) 这条边
- 3 x: 3 x : 查询 x x 所在的树的大小
- 问 x,y x , y 是否在同一棵树中
思路:
cdq
c
d
q
分治
+
+
并查集,原来以为是动态树, 写完了发现不知道怎么计算大小。。。可以考虑对整个操作序列分治,当处理这个区间的答案的时候,我们可以先递归处理
[l,mid]
[
l
,
m
i
d
]
这部分,一直到
l==r
l
==
r
,然后处理
[mid+1,r]
[
m
i
d
+
1
,
r
]
这个区间, 处理这个区间的时候,要先处理一些操作:
如果
(x,y)
(
x
,
y
)
这条边的处理在
[l,r]
[
l
,
r
]
出现过, 但是却没有在
[mid+1,r]
[
m
i
d
+
1
,
r
]
这个区间出现过时候,
solve(mid+1,r)
s
o
l
v
e
(
m
i
d
+
1
,
r
)
可以直接考虑最后出现的
(x,y)
(
x
,
y
)
是断开还是连接着的,断开不处理,连接的直接并查集合并。
如果
(x,y)
(
x
,
y
)
这条边的处理在
[l,r]
[
l
,
r
]
出现过, 在
[mid+1,r]
[
m
i
d
+
1
,
r
]
这个区间也出现过的时候,如果在
[l,mid]
[
l
,
m
i
d
]
里面
(x,y)
(
x
,
y
)
最后一次是断开着的, 那么也不用处理, 直接
solve(mid+1,r)
s
o
l
v
e
(
m
i
d
+
1
,
r
)
但是在
[l,mid]
[
l
,
m
i
d
]
最后一次是连接着的情况, 这个时候可能会影响
[mid+1,r]
[
m
i
d
+
1
,
r
]
里面的一些区间, 因为这些操作都是合法操作, 那么我们知道如果
[mid+1,r]
[
m
i
d
+
1
,
r
]
里面的一条边的操作是先断开的话,假设在
idx
i
d
x
这个位置首次断开
(x′,y′)
(
x
′
,
y
′
)
这条边,那么可以知道
[mid+1,idx]
[
m
i
d
+
1
,
i
d
x
]
这些操作的时候
(x′,y′)
(
x
′
,
y
′
)
都是连接着的,所以只要是这种情况, 任意区间
[l,r]
[
l
,
r
]
可以在
solve(l,r)
s
o
l
v
e
(
l
,
r
)
的时候先处理这种情况,即:如果在
[l,r]
[
l
,
r
]
中有边的处理有首次是断开而且下标在
[mid+1,r]
[
m
i
d
+
1
,
r
]
之间,这个时候这两个点可以先连接起来再处理
[l,mid]
[
l
,
m
i
d
]
,等处理完
[l,mid]
[
l
,
m
i
d
]
之后再恢复这些状态, 再去处理
[mid+1,r]
[
m
i
d
+
1
,
r
]
。并查集状态恢复不太好操作, 一个比较好的办法就是, 记住所有的修改序列,到时候逆向恢复状态就好了。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e5 + 10;
using namespace std;
struct P {
int op, x, y, id;
P(int o = 0, int x = 0, int y = 0, int id = 0) :
op(o), x(x), y(y), id(id) {}
void input(int i) {
id = i;
scanf("%d", &op);
if(op != 3) {
scanf("%d %d", &x, &y);
if(x > y) swap(x, y);
} else { scanf("%d", &x); y = -1; }
}
bool operator < (P p) const {
if(x != p.x) return x < p.x;
if(y != p.y) return y < p.y;
return id < p.id;
}
} rec[maxn], res[maxn], rs[maxn];
struct V {
int flag, vt, val;
V(int f = 0, int vt = 0, int v = 0) : flag(f), vt(vt), val(v) {}
};
int ans[maxn], n, m, tot;
int pre[maxn], sz[maxn];
V vec[maxn * 30];
int findset(int x) {
if(x == pre[x]) return x;
int px = findset(pre[x]);
vec[tot++] = V(0, x, pre[x]); pre[x] = px;
return pre[x];
}
void return_old(int ssz) {
while(tot > ssz) {
tot--;
int flag = vec[tot].flag;
int old = vec[tot].val;
int v = vec[tot].vt;
if(flag == 0) pre[v] = old;
else sz[v] = old;
}
}
void Merge(int x, int y) {
int nx = findset(x);
int ny = findset(y);
if(nx == ny) return ;
vec[tot++] = V(1, nx, sz[nx]);
vec[tot++] = V(0, ny, pre[ny]);
sz[nx] += sz[ny]; pre[ny] = nx;
}
void solve(int l, int r) {
int now_sz = tot, mid = (l + r) >> 1;
if(l == r && rec[l].op <= 2) return ; ///只是处理边
int num = 0;
for(int i = l; i <= r; i++) {
if(rec[i].op >= 3) continue;
rs[num++] = rec[i];
}
sort(rs, rs + num);
for(int i = 0; i < num; i++) {
int from = i;
while(from < num && rs[from].x == rs[i].x && rs[from].y == rs[i].y) from++;
///区间[l, r]中第一条边(x,y)是断边操作,也就是说这个时候x,y是连接着的,把(x,y)影响的[l,mid]的区间先合并一下
if(rs[i].id > mid && rs[i].op == 2) Merge(rs[i].x, rs[i].y);
i = from - 1;
}
if(l == r) {
if(rec[l].op == 3) {
int nx = findset(rec[l].x);
ans[l] = sz[nx];
} else if(rec[l].op == 4) {
int nx = findset(rec[l].x);
int ny = findset(rec[l].y);
ans[l] = (nx == ny);
}
return_old(now_sz);
return ;
}
solve(l, mid);
return_old(now_sz);
int cnt = 0;
for(int i = l; i <= r; i++) {
if(rec[i].op == 3 || rec[i].op == 4) continue;
res[cnt++] = rec[i];
}
sort(res, res + cnt);
for(int i = 0; i < cnt; i++) {
int from = i, flag = 0;
while(from < cnt && res[from].x == res[i].x && res[from].y == res[i].y) {
if(res[from].id > mid) flag = 1;
from++;
}
i = from - 1;
if(!flag && res[i].op == 1) { ///后面已经没有处理这条边的操作了
int nx = findset(res[i].x);
int ny = findset(res[i].y);
if(nx != ny) {
vec[tot++] = V(1, nx, sz[nx]);
vec[tot++] = V(0, ny, pre[ny]);
sz[nx] += sz[ny]; pre[ny] = nx;
}
}
}
solve(mid + 1, r);
return_old(now_sz);
}
int fac[maxn];
int main() {
while(scanf("%d %d", &n, &m) != EOF) {
for(int i = 1; i <= n; i++) { sz[i] = 1; pre[i] = i; }
for(int i = 0; i < m; i++) {
rec[i].input(i);
fac[i] = rec[i].op;
}
solve(0, m - 1);
for(int i = 0; i < m; i++) {
if(fac[i] == 3) printf("%d\n", ans[i]);
else if(fac[i] == 4) puts(ans[i] ? "Yes,cap" : "No");
}
}
return 0;
}