CF292D Connected Components
题目
给出一个图,每次删除其中一个区间内的边,询问剩下的边构成的图当中的连通块个数。
\(N\le 500,1\le M,K\le 10000\)
分析
维护一个前缀的并查集和后缀的并查集,然后可以考虑每次拿出两段来合并,于是就很容易了。
具体可以见代码,时间复杂度是 \(O(kn\alpha(n))\)
代码
#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T &x){
x=0;bool f=false;char ch=getchar();
while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
while(isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
x=f?-x:x;
return ;
}
template<typename T>
inline void write(T x){
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar(x%10^48);
return ;
}
const int N=505,M=1e4+5;
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define mp make_pair
#define ll long long
int n,m,k;
int l[M][N],r[M][N],fa[N];
int Getfa1(int id,int x){return l[id][x]==x?x:l[id][x]=Getfa1(id,l[id][x]);}
int Getfa2(int id,int x){return r[id][x]==x?x:r[id][x]=Getfa2(id,r[id][x]);}
int Getfa(int x){return fa[x]==x?x:fa[x]=Getfa(fa[x]);}
PII a[M];
signed main(){
read(n),read(m);
for(register int i=1;i<=n;i++) l[0][i]=r[m+1][i]=i;
for(register int i=1;i<=m;i++) read(a[i].fi),read(a[i].se);
for(register int i=1;i<=m;i++){
for(register int j=1;j<=n;j++) l[i][j]=l[i-1][j];
int x=Getfa1(i,l[i][a[i].fi]),y=Getfa1(i,l[i][a[i].se]);
if(x==y) continue;
l[i][x]=y;
}
for(register int i=m;i>=1;i--){
for(register int j=1;j<=n;j++) r[i][j]=r[i+1][j];
int x=Getfa2(i,r[i][a[i].fi]),y=Getfa2(i,r[i][a[i].se]);
if(x==y) continue;
r[i][x]=y;
}
read(k);
while(k--){
int lc,rc,Ans=0;
read(lc),read(rc);
for(register int i=1;i<=n;i++) fa[i]=l[lc-1][i];
for(register int i=1;i<=n;i++){
int x=Getfa(i),y=Getfa(r[rc+1][i]);
if(x==y) continue;
fa[x]=y;
}
for(register int i=1;i<=n;i++) if(Getfa(i)==i) Ans++;
write(Ans),putchar('\n');
}
return 0;
}
扩展
更优秀的解法:gyh20的博客CF292D
使用主席树和 \(LCT\) 进行在线的处理,做到了 \(O((n+m+q)\log n)\)