luogu P5227 [AHOI2013]连通图
题面传送门
一开始傻子一样的写了个线段树分治+LCT常数巨大只得了\(50\)分和没有按秩合并的并查集一样分数。
然后我们发现这个东西删掉的边数很少,所以如果每一次都把这个东西重新初始化一边很不划算。
然后可以发现有很多边在一大段区间内都是存在的。
所以就可以上线段树分治了,连通性用并查集维护。
这个是\(O(nlog^2n)\)
但是还有更优秀的离线做法:LCT维护删除时间最大生成树。
那么这个就可以做到常数巨大\(O(nlogn)\)了。
code:
#include<bits/stdc++.h>
#define I inline
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define beg(x) int cur=s.h[x]
#define end cur
#define go cur=tmp.z
#define l(x) x<<1
#define r(x) x<<1|1
#define N 200039
#define ll long long
using namespace std;
int n,m,k,x[N],y[N],z,c,cnt,last[N],ans[N],un,wn;
vector<int> f[4*N];
int fa[N],st[N],sh,siz[N];
I int find(int x){return fa[x]==x?x:find(fa[x]);}
I void swap(int &x,int &y){x^=y^=x^=y;}
I void merge(int x,int y){
un=find(x);wn=find(y);
(un^wn)&&((siz[un]>siz[wn])&&(swap(un,wn),0),st[++sh]=un,fa[un]=wn,siz[wn]+=siz[un],cnt++);
}
I void get(int x,int y,int z,int l=1,int r=k,int now=1){
if(x<=l&&r<=y) return (void)(f[now].push_back(z));int m=l+r>>1;
(x<=m)&&(get(x,y,z,l,m,l(now)),0);(y>m)&&(get(x,y,z,m+1,r,r(now)),0);
}
I void find(int l=1,int r=k,int now=1){
int i,tmp,lasttop=sh;for(i=0;i<f[now].size();i++)tmp=f[now][i],merge(x[tmp],y[tmp]);
int m=l+r>>1;l^r?(find(l,m,l(now)),find(m+1,r,r(now)),0):(ans[l]=cnt);
while(sh^lasttop) tmp=st[sh--],siz[fa[tmp]]-=siz[tmp],fa[tmp]=tmp,cnt--;
}
int main(){
freopen("1.in","r",stdin);
register int i;scanf("%d%d",&n,&m);
for(i=1;i<=n;i++) fa[i]=i,siz[i]=1;
for(i=1;i<=m;i++) scanf("%d%d",&x[i],&y[i]),last[i]=1;
scanf("%d",&k);
for(i=1;i<=k;i++){
scanf("%d",&c);while(c--) scanf("%d",&z),(i^last[z])&&(get(last[z],i-1,z),0),last[z]=i+1;
}
for(i=1;i<=m;i++)(last[i]!=k+1)&&(get(last[i],k,i),0);find();
for(i=1;i<=k;i++) printf("%s\n",ans[i]==n-1?"Connected":"Disconnected");
}