LGP5443 [APIO 2019] 桥梁 学习笔记
LGP5443 [APIO 2019] 桥梁 学习笔记
题意简述
给定一个 \(n\) 点 \(m\) 边的简单无向有权图。有 \(q\) 次操作,分为两种类型:
- 修改一条边的边权
- 问以 \(s\) 为起点,在经过的边权不小于 \(w\) 的情况下,可达点的数量。
\(n\le 5\times 10^4,0\le m\le 10^5,1\le q\le 10^5\)。
做法解析
这是个图上给定修改和询问序列的问题。看起来“边权约束下的可达点数”不是很好做。众所周知,操作序列这东西,简单的就用 \(\log\),复杂的就用 \(\text{sqrt}\)。这题看起来比较复杂,所以果断往根号上想,对操作序列分块。
既然是对操作序列分块,复杂度应该就形如 \(O(q\sqrt{q}k)\) 了。这道题 \(q\le 10^5\),不过时限为 \(\text{2.00s}\),所以大胆想大胆写就对了!下文设块长为 \(B\)。
具体来说,当我们处理“这一块”时,我们要做什么?首先,在这一块之前的边权修改是可以直接做的(实现时我们把它放在上一块处理完后时做);这段里被修改的边我们单独拿出来放到一个数组里,对于每一个询问我们都暴力处理这些边,并按照限重从大到小的顺序处理询问(这样的话可用的边是不断变多的,图连通性问题增易减难)。显然询问和带修边都是 \(O(B)\) 级别的,所以这里的复杂度是 \(O(B^2\log n)\);当然,每次处理这块前还要再给那个大的边集排个序。
这样,每块处理的复杂度就是 \(m\log m+B^2\log n\)。总时间复杂度 \(\frac{q}{B}(m\log m+B^2\log n)\),\(B\) 取 \(\sqrt{q}\) 时复杂度为 \(q\sqrt{q}\log n+\sqrt{q}m\log m\approx q\sqrt{q}\log m\)。
不过,块处理的末尾对当前块的修改进行“永久化”后的排序可以用归并来做,这样的话排序处的复杂度可以从单块 \(O(m\log m)\) 优化到 \(O(m+B\log B)\),总时间复杂度就可以做到 \(O(q\sqrt{q\log m})\) 左右了。不过我不太会实现,而且这优化幅度似乎也不值当。
代码实现
代码难度不算高。中位紫水平。
注意一条边可能在一个块内被修改多次!
我也不知道这个块长是怎么个调法,但似乎 \(B=2^{10}\) 确实是最快的。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=5e4+5,MaxM=1e5+5,MaxQ=1e5+5;
int N,M,Q,X,Y,Z,bsiz,Opt,ans[MaxQ],vis[MaxM];
struct edge{int u,v,w,id;}E[MaxM];
bool ecmpw(edge a,edge b){return a.w>b.w;}
void addedge(int u,int v,int w,int id){E[id]={u,v,w,id};}
struct anob{int id,w,t;};
bool acmpw(anob a,anob b){return a.w>b.w;}
vector<anob> mvec,qvec;
struct RevocaUnionFind{
int n,ufa[MaxN],usz[MaxN],tim;
struct oper{int fu,fv,hc;}mem[MaxN];
void init(int x){n=x,tim=0;for(int i=0;i<=n;i++)ufa[i]=i,usz[i]=1;}
int find(int u){return ufa[u]==u?u:find(ufa[u]);}
void merge(int u,int v){
int fu=find(u),fv=find(v);
if(fu==fv)return;if(usz[fu]<usz[fv])swap(fu,fv);
int hc=usz[fv];ufa[fv]=fu,usz[fu]+=hc;
mem[++tim]={fu,fv,hc};
};
void revoke(int p){
for(;tim>p;tim--){
auto [fu,fv,hc]=mem[tim];
ufa[fv]=fv,usz[fu]-=hc;
}
}
}RevUf;
int trg1(){return RevUf.tim;}
int trg2(int i){return RevUf.usz[i];}
int mp[MaxM];
void solve(){
sort(E+1,E+M+1,ecmpw),vsorter(qvec,acmpw);
RevUf.init(N);for(int i=1;i<=M;i++)mp[E[i].id]=i;
int p=1;for(anob cq : qvec){
for(anob ca : mvec)vis[ca.id]=E[mp[ca.id]].w;
for(;p<=M;p++){
auto [eu,ev,ew,eid]=E[p];
if(ew<cq.w)break;
if(!vis[eid])RevUf.merge(eu,ev);
}
int ltim=trg1();
for(anob ca : mvec){
if(ca.t>cq.t)break;
vis[ca.id]=ca.w;
}
for(anob ca : mvec)if(vis[ca.id]>=cq.w)RevUf.merge(E[mp[ca.id]].u,E[mp[ca.id]].v);
ans[cq.t]=trg2(RevUf.find(cq.id));
RevUf.revoke(ltim);
}
for(anob ca : mvec)E[mp[ca.id]].w=ca.w,vis[ca.id]=0;
mvec.clear(),qvec.clear();
}
int main(){
readi(N),readi(M);
for(int i=1;i<=M;i++)readi(X),readi(Y),readi(Z),addedge(X,Y,Z,i);
readi(Q);bsiz=1<<10;
for(int i=1;i<=Q;i++){
readi(Opt),readi(X),readi(Y);
anob tmp={X,Y,i};
if(Opt==1)mvec.push_back(tmp);
if(Opt==2)qvec.push_back(tmp);
if(i%bsiz==0)solve();
}
if(Q%bsiz)solve();
for(int i=1;i<=Q;i++)if(ans[i])writi(ans[i]),puts("");
return 0;
}
反思总结
操作序列这东西,简单的就用 \(\log\),复杂的就用 \(\text{sqrt}\)。
考虑到这玩意在图上,也许佐以一个可撤销并查集就可以维护信息了;(当然,也有可能要用 bitset,比如追忆!)
浙公网安备 33010602011771号