2019牛客暑期多校训练营(第八场) E 线段树+可撤销并查集
题意:
给出m条无向边,每条边都有一个$[l,r]$,意思是体积在这个范围内的人才能通过这条边,询问有多少种体积的可能性,能使人从1到n
思路:由于是无向边,1和n的连通性可以用并查集维护。
考虑最暴力的做法,枚举每一种体积,将当前体积能通过的边用并查集维护一下,判断1到n的连通性即可。
考虑优化,首先区间必定可以离散化,并且我们可以将离散化后的区间按照线段树的方式划分一下,然后将每一个子区间的边用并查集连一下,判断连通性,然后离开这个子区间时,将并查集的连边操作退回就可以了。
由于每条边最多被划分成log个区间,也就是会被并查集加入log次,拆开log次,所以时间复杂度是(n*logn*logn),其中一个log是并查集带来的,常数很小。
可撤销的并查集用按秩合并来完成,并且只能回退连续一段时间的操作,离散化后点有2*n个,线段树点不要开太少。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) #define dep(i,b,a) for(int i=b;i>=a;i--) #define pb(x) push_back(x) #define pii pair<int,int > using namespace std; const int maxn=200010; vector<int >tr[maxn<<2]; int n,m; struct edge{ int u,v,l,r; }a[maxn]; int b[maxn<<1],cnt,ans; int fa[maxn],siz[maxn]; int find(int x){ return x==fa[x]?x:find(fa[x]); } void update(int o,int l,int r,int ql,int qr,int i){ if(ql<=l&&r<=qr){ tr[o].pb(i); return; } int mid=(l+r)>>1; if(ql<=mid)update(o<<1,l,mid,ql,qr,i); if(mid<qr)update(o<<1|1,mid+1,r,ql,qr,i); } void dfs(int o,int l,int r){ vector<pii >ve; int si=tr[o].size(); rep(i,0,si-1){ int id=tr[o][i]; int u=a[id].u,v=a[id].v; int fu=find(u),fv=find(v); if(fu==fv)continue; if(siz[fu]>siz[fv])swap(fu,fv); fa[fu]=fv; int d=0; if(siz[fu]==siz[fv])d++; siz[fv]+=d; ve.push_back({fu,d}); } int mid=(l+r)>>1; if(find(1)==find(n)){ ans+=b[r+1]-b[l]; }else if(l<r){ dfs(o<<1,l,mid); dfs(o<<1|1,mid+1,r); } si=ve.size(); dep(i,si-1,0){ siz[fa[ve[i].first]]-=ve[i].second; fa[ve[i].first]=ve[i].first; } } int main(){ cin>>n>>m; rep(i,1,n){ fa[i]=i; siz[i]=1; } for(int i=1;i<=m;i++){ scanf("%d%d%d%d",&a[i].u,&a[i].v,&a[i].l,&a[i].r); b[++cnt]=a[i].l; b[++cnt]=a[i].r+1; } sort(b+1,b+1+cnt); cnt=unique(b+1,b+1+cnt)-b-1; for(int i=1;i<=m;i++){ a[i].l=lower_bound(b+1,b+1+cnt,a[i].l)-b; a[i].r=lower_bound(b+1,b+1+cnt,a[i].r+1)-b; update(1,1,cnt,a[i].l,a[i].r-1,i); } dfs(1,1,cnt); printf("%d\n",ans); }
——愿为泰山而不骄
qq850874665~~