Link
Difficulty
算法难度7,思维难度7,代码难度7
Description
给定一个无向图,有 n n n个点, m m m条边,边从 1 1 1到 m m m标号
q q q次询问,每次询问 [ l , r ] [l,r] [l,r]内的边组成的图的连通块个数
1 ≤ n , m , q ≤ 2 × 1 0 5 1\le n,m,q \le 2\times 10^5 1≤n,m,q≤2×105
Solution
这题听说可以树套树,但是会TLE
以下都是神仙做法:
我们维护按照编号加边,维护边的最大生成树,边的权值就是编号
每次如果没有形成环就直接加,如果有环就替换掉最小的那条边,可以LCT维护
我们记第 i i i条边替换的边的序号为 n t r i ntr_i ntri,特别的,自环的 n t r = m ntr=m ntr=m,直接加的边的 n t r = 0 ntr=0 ntr=0
查询的时候直接查 [ l , r ] [l,r] [l,r]内 n t r i < l ntr_i<l ntri<l的个数,答案就是 n n n减去这个个数,可以主席树维护
接下来我们尝试证明这个结论的正确性:
我们考虑如果一条位于 [ l , r ] [l,r] [l,r]内的边的 n t r ntr ntr取不同的值时,它对答案的贡献
-
当 n t r i = 0 ntr_i=0 ntri=0时,也就是可以直接加上这条边,那么连通块个数会-1
-
当 0 < n t r i < l 0<ntr_i<l 0<ntri<l时,也就是它替换掉了前面出现过的一条边,并且这条边的编号 < l <l <l
你可能会说:“它没有贡献啊,它明明替换了一条边,怎么会有贡献呢?”
但是你会发现它替换的边的编号 < l <l <l,那么这时这条边已经不存在了
也就是说现在这条边就可以替代它的作用了,这时它就可以有1的贡献了
-
当 l ≤ n t r i ≤ r l\le ntr_i\le r l≤ntri≤r时,也就是说它替换了一条前面出现的边,并且这条边的编号 ≥ l \ge l ≥l
和前面同理,只不过这时它替换的边是存在的,那么现在这条边就没有贡献了
-
当 n t r i = m ntr_i=m ntri=m时,也就是说这条边是一个自环,那么它一定不会有贡献
这样我们就求得了 [ l , r ] [l,r] [l,r]内的边对答案的贡献了,就可以计算答案了
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),本人在bzoj上跑了32s,写起来应该没什么常数问题
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#define LL long long
using namespace std;
inline int read(){
int x=0,f=1;char ch=' ';
while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0' && ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f==1?x:-x;
}
const int N=4e5+5;
namespace LCT{
int ch[N][2],f[N],rev[N],mn[N],id[N];
inline bool get(int x){return ch[f[x]][1]==x;}
inline void pushup(int x){mn[x]=min(id[x],min(mn[ch[x][0]],mn[ch[x][1]]));}
inline bool nroot(int x){return ch[f[x]][0]==x || ch[f[x]][1]==x;}
inline void reverse(int x){rev[x]^=1;swap(ch[x][0],ch[x][1]);}
inline void pushdown(int x){
if(rev[x]){
if(ch[x][0])reverse(ch[x][0]);
if(ch[x][1])reverse(ch[x][1]);
rev[x]=0;
}
}
inline void pd(int x){if(nroot(x))pd(f[x]);pushdown(x);}
inline void rotate(int x){
int y=f[x],z=f[y],w=get(x);
f[x]=z;if(nroot(y))ch[z][get(y)]=x;
ch[y][w]=ch[x][w^1];f[ch[y][w]]=y;
ch[x][w^1]=y;f[y]=x;
pushup(y);pushup(x);
}
inline void Splay(int x){
pd(x);
for(int fa=f[x];nroot(x);rotate(x),fa=f[x])
if(nroot(fa))
rotate((get(fa)==get(x))?fa:x);
}
inline void access(int x){for(int y=0;x;x=f[y=x])Splay(x),ch[x][1]=y,pushup(x);}
inline void makeroot(int x){access(x);Splay(x);reverse(x);}
inline int findroot(int x){access(x);Splay(x);while(ch[x][0])x=ch[x][0];return x;}
inline void split(int x,int y){makeroot(x);access(y);Splay(y);}
inline void link(int x,int y){split(x,y);f[x]=y;}
inline void cut(int x,int y){split(x,y);f[x]=ch[y][0]=0;}
};
using namespace LCT;
int n,m,q;
int X[N],Y[N],ntr[N];
int sum[N*30],ls[N*30],rs[N*30],sz,root[N];
inline void insert(int &now,int pre,int l,int r,int pos){
now=++sz;
sum[now]=sum[pre]+1;
ls[now]=ls[pre];
rs[now]=rs[pre];
if(l==r)return;
int mid=(l+r)>>1;
if(pos<=mid)insert(ls[now],ls[pre],l,mid,pos);
else insert(rs[now],rs[pre],mid+1,r,pos);
}
inline int query(int now,int pre,int l,int r,int pos){
if(l==r)return sum[now]-sum[pre];
int mid=(l+r)>>1;
if(pos<=mid)return query(ls[now],ls[pre],l,mid,pos);
else return query(rs[now],rs[pre],mid+1,r,pos)+sum[ls[now]]-sum[ls[pre]];
}
int main(){
memset(mn,0x3f,sizeof mn);
memset(id,0x3f,sizeof id);
n=read();m=read();q=read();int type=read();
for(int i=1;i<=m;++i){
X[i]=read();
Y[i]=read();
mn[i+n]=id[i+n]=i;
if(X[i]==Y[i]){
ntr[i]=m;
insert(root[i],root[i-1],0,m,ntr[i]);
continue;
}
int fx=findroot(X[i]),fy=findroot(Y[i]);
if(fx!=fy){
link(i+n,X[i]);
link(i+n,Y[i]);
ntr[i]=0;
}
else{
split(X[i],Y[i]);
int id=mn[Y[i]];
cut(id+n,X[id]);
cut(id+n,Y[id]);
link(i+n,X[i]);
link(i+n,Y[i]);
ntr[i]=id;
}
insert(root[i],root[i-1],0,m,ntr[i]);
}
int lastans=0;
for(int i=1;i<=q;++i){
int l=read()^lastans,r=read()^lastans;
printf("%d\n",lastans=n-query(root[r],root[l-1],0,m,l-1));
if(!type)lastans=0;
}
return 0;
}