大致题意: 一张\(n\)个点\(m\)条边的无向图,求当只保留编号在\([l,r]\)内的边时的连通块个数。(强制在线)
前言
\(Jan\ 29th\)刷题计划(2/6),算法标签:LCT、主席树。
真的是很妙的一道题啊!
分析一条边的贡献
设想在十分理想的状态下,这些边刚好能构成森林,则连通块个数就是\(n\)减边数。
也就是说,此时每条边的贡献是\(1\)。
然而,现实是残酷的,一旦你新加入的边与已有的边形成了环,它的贡献就变成了\(0\)。
即,一条边的贡献,取决于它是否构成环。
考虑我们将边一条一条加到图上,当加入某一条边形成了第一个环的时候,我们会发现,只要删掉环上的某一条边,新加入的边贡献就为\(1\),否则贡献为\(0\)。
设这个环上编号最小的边的编号为\(Mn\),则新加的边的贡献,就取决于编号为\(Mn\)的边是否存在。
即,只有当询问区间的左端点大于\(Mn\)时,新加的这条边才会有贡献。
然后我们删去这条编号为\(Mn\)的边,并加上新的这条边,于是这张图又成了森林。
接下来,我们重复上述操作,继续加边,直到加入某一条边又形成了一个环,则又按照上面的方式再次处理。
这样,我们就能知道,每条边产生贡献时对左端点的要求\(f_i\)(对于加到图中不会产生环的边,\(f_i=0\))。
而这个\(f_i\)可以通过用\(LCT\)模拟上述操作求得。
如何处理询问
假设我们询问一个区间\([l,r]\),则这一区间内边能产生的贡献就是其中\(f_i<l\)的边的个数,然后用\(n\)减去这一贡献就是答案。
显然直接用主席树就可以解决这道题了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define LN 20
#define min(x,y) ((x)<(y)?(x):(y))
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,m;struct line {int x,y;}s[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class LinkCutTree//LCT
{
private:
#define SZ 2*N
#define PU(x) (O[x].Mn=min(O[x].V,min(O[O[x].S[0]].Mn,O[O[x].S[1]].Mn)))
#define PD(x) (O[x].R&&(Re(O[x].S[0]),Re(O[x].S[1]),O[x].R=0))
#define Re(x) (x)&&(swap(O[x].S[0],O[x].S[1]),O[x].R^=1)
#define IR(x) (O[O[x].F].S[0]^(x)&&O[O[x].F].S[1]^(x))
#define Wh(x) (O[O[x].F].S[1]==(x))
#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
#define MR(x) (Ac(x),S(x),Re(x))
#define Sp(x,y) (MR(x),Ac(y),S(y))
int St[SZ+5];struct node {int V,Mn,R,F,S[2];}O[SZ+5];
I void Ro(int x)
{
RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x),
O[x].F=p,Co(O[x].S[d^1],f,d),Co(f,x,d^1),PU(f);
}
I void S(int x)
{
RI f=x,T=0;W(St[++T]=f,!IR(f)) f=O[f].F;W(T) PD(St[T]),--T;
W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(x)^Wh(f)?x:f),0),Ro(x);PU(x);
}
I void Ac(int x) {for(RI y=0;x;x=O[y=x].F) S(x),O[x].S[1]=y,PU(x);}
I int FR(int x) {Ac(x),S(x);W(O[x].S[0]) x=O[x].S[0];return S(x),x;}
I void Link(CI x,CI y) {MR(x),FR(y)^x&&(O[x].F=y);}
I void Cut(CI x,CI y) {MR(x),FR(y)==x&&O[y].F==x&&!O[y].S[0]&&(O[x].S[1]=O[y].F=0,PU(x));}
public:
I void Init()//把边看成点,初始化点权
{
for(RI i=1;i<=n;++i) O[i].V=O[i].Mn=m+1;O[0].Mn=m+1;//点的点权设成极大值
for(RI i=1;i<=m;++i) O[n+i].V=O[n+i].Mn=i;//边的点权为边的编号
}
I int Try(CI id)//尝试加入一条新边
{
RI x=s[id].x,y=s[id].y,t=0;FR(x)==FR(y)&&//如果形成了环
(Sp(x,y),t=O[y].Mn,Cut(s[t].x,t+n),Cut(t+n,s[t].y),0);//删去编号最小的边
return Link(x,id+n),Link(id+n,y),t;//连边
}
}S;
class ChairmanTree
{
private:
#define LT l,mid,O[rt1].S[0],O[rt2].S[0]
#define RT mid+1,r,O[rt1].S[1],O[rt2].S[1]
int Nt,Rt[N+5];struct node {int V,S[2];}O[N*LN+5];
I void Ins(CI x,CI l,CI r,int& rt1,CI rt2)
{
if(O[rt1=++Nt]=O[rt2],++O[rt1].V,l==r) return;
int mid=l+r>>1;x<=mid?Ins(x,LT):Ins(x,RT);
}
I int Qry(CI x,CI y,CI l,CI r,CI rt1,CI rt2)
{
if(x<=l&&r<=y) return O[rt2].V-O[rt1].V;int mid=l+r>>1;
return (x<=mid?Qry(x,y,LT):0)+(y>mid?Qry(x,y,RT):0);
}
public:
I void Ins(CI v,CI x) {Ins(x,0,m,Rt[v],Rt[v-1]);}//单点修改
I int Qry(CI x,CI y) {return Qry(0,x-1,0,m,Rt[x-1],Rt[y]);}//区间查询
}C;
int main()
{
RI Qt,Ty,i,x,y,lst=0;F.read(n),F.read(m),F.read(Qt),F.read(Ty),S.Init();//读入,初始化LCT
for(i=1;i<=m;++i) F.read(s[i].x),F.read(s[i].y),C.Ins(i,s[i].x^s[i].y?S.Try(i):m);//求出每条边对左端点的要求,在主席树上修改
W(Qt--) F.read(x),F.read(y),F.writeln(lst=n-C.Qry(x^(Ty*lst),y^(Ty*lst)));//处理询问
return F.clear(),0;
}