LGP5443 [APIO 2019] 桥梁 学习笔记

LGP5443 [APIO 2019] 桥梁 学习笔记

Luogu Link

题意简述

给定一个 \(n\)\(m\) 边的简单无向有权图。有 \(q\) 次操作,分为两种类型:

  1. 修改一条边的边权
  2. 问以 \(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,比如追忆!)

posted @ 2025-02-20 20:10  矞龙OrinLoong  阅读(6)  评论(0)    收藏  举报