题目大意
给定
N
个点,
N,M,Q≤2∗105
解题思路
这种每次询问一个区间的问题一般都可以样莫队在 O(QM−−√) 的时间得出答案。但是这题维护的是联通块,而联通块的问题通常是用并查集来做,但是并查及不能支持撤销操纵,那么我们就要完善一下方法了。
如果直接每次重构并查集,那么就是暴力了。我们知道对于莫队中的一个块,假如左端点所在的块是不变的,那么右端点是单调递增的,而且对于一个块中的左端点可能的就只有 M−−√ 个位置。假如我们每次重构左端点所在的块,然后右边的部分再单调维护,那么复杂度就可以过。但左边的部分依旧要实现撤销操作。
那么我们可以对右边的部分开一个并查集。每次修改完右边后再考虑左边对这个并查集的影响。有与左边的最多只有 M−−√ 个修改,那么我们把这些修改放到右边维护的并查集上,统计答案。最后再把左边的影响去掉,具体的就只需要再开一个并查集,对于每一次询问用标记维护当前的点在这次询问中有没有被访问过,有的话证明已近更新了值了,直接做并查集。没有的话,就先更新成新的值,再做并查集(注意这里的并查集跟上面右部分的并查集是不一样的)。
程序
//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
const int MAXN = 2e5 + 5;
struct Node {
int l, r, id, blo;
} P[MAXN];
int Q[MAXN][2], Flag[MAXN];
int N, M, Qu, blo, siz, Ans, Pre[MAXN], Fa[MAXN], S[MAXN];
bool Cmp(Node A, Node B) {
return A.blo < B.blo || (A.blo == B.blo && A.r < B.r);
}
int Get(int Now) {
if (Pre[Now] == Now) return Now;
Pre[Now] = Get(Pre[Now]);
return Pre[Now];
}
int Get2(int Now, int tag) {
if (Flag[Now] != tag) Fa[Now] = Pre[Now], Flag[Now] = tag;
if (Fa[Now] == Now) return Now;
Fa[Now] = Get2(Fa[Now], tag);
return Fa[Now];
}
int main() {
scanf("%d%d%d", &N, &M, &Qu);
for (int i = 1; i <= N; i ++) scanf("%d%d", &Q[i][0], &Q[i][1]);
siz = sqrt(M);
for (int i = 1; i <= Qu; i ++) {
scanf("%d%d", &P[i].l, &P[i].r);
P[i].blo = (P[i].l - 1) / siz + 1;
P[i].id = i;
}
sort(P + 1, P + 1 + Qu, Cmp);
int r;
for (int i = 1; i <= Qu; i ++) {
if (P[i].blo != P[i - 1].blo || i == 1) {
Ans = N;
for (int j = 1; j <= N; j ++) Pre[j] = j;
r = P[i].blo * siz + 1;
}
for (; r <= P[i].r; r ++) {
int fx = Get(Q[r][0]), fy = Get(Q[r][1]);
if (fx != fy) Ans --;
Pre[fx] = fy;
}
int ans = Ans;
for (int l = min(P[i].r, P[i].blo * siz); l >= P[i].l; l --) {
int fx = Get2(Q[l][0], i), fy = Get2(Q[l][1], i);
if (fx != fy) ans --;
Fa[fx] = fy;
}
S[P[i].id] = ans;
}
for (int i = 1; i <= Qu; i ++) printf("%d\n", S[i]);
}