传送门
这道题也是bzoj3514离线版
题意:
给你n个点,m条边,询问k个区间[L,R],求只保留[L,R]间的边,有多少个联通块。n,m,k<=200000
Solution:
既然是离线那么显然可以搞事,询问区间有什么暴力方法呢?莫队!因为在图上所以每次操作不是 O(1) ,感觉似乎可以用并查集的样子呢,算一下复杂度为 O(nn√α) 也是可以接受的。但是并查集怎么做呢?一开始一直在考虑删边的问题,但是那就需要可持久化并查集,复杂度就不会特别优秀而且正确性不敢保证,这时候我们就要想办法避免掉删边的操作:我们回顾一下莫队算法的思想,按照左端点所在的块为第一关键字,右端点为第二关键字排序。这保证了左端点所在的块中加的边数是不固定的,后面的块中加的边数一定是递增的,那么我们就可以操作了:枚举每一块,求出左端点在这一块内的操作是那些,按照右端点排序,先用一个并查集 f1 维护不在当前枚举的这一块的边,然后用另一个并查集 f2 每次暴力维护左端点到当前块末端的边,对于每一块 f1 是 O(nα) 的,对于所有操作 f2 是 O(kn√α) 的,保证了复杂度,具体如何维护见代码。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int fa[200010],f[200010],st[200010],cnt;
int n,m,qu,T,kn,nans;
struct Q{
int l,r,pos,id;
}q[200010];
struct edg{
int u,v;
}e[200010];
int pos[200010];
int ans[200010];
bool cmp(Q a,Q b)
{
if (pos[a.l]==pos[b.l]) return a.r<b.r;
else return a.l<b.l;
}
int find(int x)
{
if (f[x]!=x) f[x]=find(f[x]);
return f[x];
}
int find2(int x)
{
if (fa[x]!=x) fa[x]=find2(fa[x]);
return fa[x];
}
void baoli(int x)
{
int u,v;
ans[q[x].id]=nans;
for (int i=q[x].l;i<=q[x].r;i++)
{
u=find2(e[i].u);v=find2(e[i].v);
if (u!=v)
{
ans[q[x].id]--;
st[++cnt]=u;
fa[u]=v;
}
}
for (int i=1;i<=cnt;i++) fa[st[i]]=st[i];
}
int main()
{
scanf("%d",&T);
while (T--)
{
scanf("%d%d%d",&n,&m,&qu);
kn=sqrt(m);
int sj=(m-1)/kn+1;
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=m;i++)
pos[i]=(i-1)/kn+1;
for (int i=1;i<=m;i++)
scanf("%d%d",&e[i].u,&e[i].v);
for (int i=1;i<=qu;i++)
scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
sort(q+1,q+1+qu,cmp);
int L,R,last=0,p,nr;
for (int i=1;i<=sj;i++)
{
L=last+1;R=last;p=kn*i;nr=p;
nans=n;
while (pos[q[R+1].l]==i&&R<qu) R++;
if (L>R) continue;
for (int ii=1;ii<=n;ii++) f[ii]=ii;
for (int nw=L;nw<=R;nw++)
{
if (q[nw].r<=p)
{
baoli(nw);
continue;
}
int u,v;
for (int j=nr+1;j<=q[nw].r;j++)
{
u=find(e[j].u);
v=find(e[j].v);
if (u!=v)
{
nans--;
f[u]=v;
}
}
nr=q[nw].r;
cnt=0;
ans[q[nw].id]=nans;
for (int j=q[nw].l;j<=p;j++)
{
u=find(e[j].u);v=find(e[j].v);//注意这里是find
if (u!=v&&find2(u)!=find2(v))
{
ans[q[nw].id]--;
st[++cnt]=find2(u);
fa[find2(u)]=find2(v);
}
}
for (int ii=1;ii<=cnt;ii++) fa[st[ii]]=st[ii];
}
last=R;
}
for (int i=1;i<=qu;i++)
printf("%d\n",ans[i]);
}
}