【洛谷5443】[APIO2019] 桥梁(操作分块)
- 给定一张\(n\)个点\(m\)条边的无向图,每条边有一个边权。
- 有\(q\)次操作,分为两种:修改一条边的边权;求从一个点出发只走边权小于等于\(w\)的边能到达多少个点。
- \(n\le5\times10^4,m,q\le10^5\)
没有修改时的离线做法
考虑没有修改的情况,直接按权值从大到小把所有的边和询问都排个序。
然后按序枚举,则处理到一个询问时所有权值大于等于它的边都已被加入并查集,直接询问所在连通块大小即可。
但现在有修改,肯定不能直接这样全局离线搞了。
操作分块
考虑把操作分块,设大小为\(S\)。
枚举到一个块时,我们已经处理好之前所有块的修改。
而对于这个块,修改个数不超过\(S\)个,我们不妨直接记下所有会被修改到的特殊边,而剩余的边值都固定不变。
仿照之前的做法,我们按权值从大到小把所有的固定边和询问都排个序。
则处理到一个询问的时候,所有权值大于等于它的固定边都已被加入并查集,而会被修改到的特殊边最多只有\(S\)条,可以枚举每条特殊边判断边权是否大于等于它,若是则加入并查集中,处理完该询问后再撤销即可。
复杂度分析为\(O(\frac qSmlogn+qSlogn)\),理论上取\(S=\sqrt q\)得到最优复杂度,但实际上取\(S=\sqrt{qlogn}\)似乎更快。。。
代码:\(O(q\sqrt qlogn)\)
#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 50000
#define M 100000
using namespace std;
int n,m,ans[M+5];struct edge {int x,y,w;I bool operator < (Con edge& o) Con {return w>o.w;}}o[M+5],p[M+5];
struct Q {int id,x,w;I bool operator < (Con Q& o) Con {return w>o.w;}}q[M+5];struct OP {int id,x,w,u;}z[M+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
namespace U//按秩合并可撤销并查集
{
int f[N+5],g[N+5],T,Sx[M+5],Sy[M+5];I void Init() {for(RI i=1;i<=n;++i) g[f[i]=i]=1;}
I int fa(CI x) {return f[x]^x?fa(f[x]):x;}
I void Union(RI x,RI y,CI op=0)//合并,op=1表示要撤销
{
if((x=fa(x))==(y=fa(y))) return;g[x]<g[y]&&(swap(x,y),0),g[f[y]=x]+=g[y],op&&(Sx[++T]=x,Sy[T]=y);
}
I void Back() {W(T) g[Sx[T]]-=g[f[Sy[T]]=Sy[T]],--T;}//撤销
}
I void Solve(CI t1,CI t2)
{
RI i,j,k,l;for(i=1;i<=m;++i) p[i]=o[i];for(k=1;k<=t1;++k) p[z[k].x].w=-1;//把可能会改变的边边权设为-1
for(U::Init(),sort(p+1,p+m+1),sort(q+1,q+t2+1),i=j=1;i<=t2;++i)
{
W(j<=m&&p[j].w>=q[i].w) U::Union(p[j].x,p[j].y),++j;//双指针,使所有边权大于等于当前询问权值的边都被加入
for(k=1;k<=t1&&z[k].id<q[i].id;++k) z[k].u=o[z[k].x].w,o[z[k].x].w=z[k].w;//枚举所有修改操作修改
for(l=1;l<=t1;++l) o[z[l].x].w>=q[i].w&&(U::Union(o[z[l].x].x,o[z[l].x].y,1),0);//枚举所有特殊边判断是否加入并查集
ans[q[i].id]=U::g[U::fa(q[i].x)],U::Back();for(--k;k;--k) o[z[k].x].w=z[k].u;//记录答案,然后撤销特殊边影响
}
for(k=1;k<=t1;++k) o[z[k].x].w=z[k].w;//修改
}
int main()
{
RI Qt,sz,i;for(read(n,m),i=1;i<=m;++i) read(o[i].x,o[i].y,o[i].w);
RI op,x,y,t1=0,t2=0;for(read(Qt),sz=max((int)sqrt(Qt*log2(n)),1),i=1;i<=Qt;++i) read(op,x,y),
op==1?(z[++t1]=(OP){i,x,y},0):(q[++t2]=(Q){i,x,y},0),(!(i%sz)||i==Qt)&&(Solve(t1,t2),t1=t2=0);//每sz个操作一起做
for(i=1;i<=Qt;++i) ans[i]&&(writeln(ans[i]),0);return clear(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒