当前位置: 首页 > news >正文

LGP11369 [Ynoi 2024-R] 弥留之国的爱丽丝 学习笔记

LGP11369 [Ynoi 2024-R] 弥留之国的爱丽丝 学习笔记

Luogu Link

前言

又是 bitset 维护 \(\texttt{DAG}\) 可达性,又是操作序列分块的,大典题呐看来这是。

题意简述

给定一个 \(n\)\(m\) 边的有向图。边有黑白两种颜色。一开始所有边都是黑色的。

\(q\) 次操作:

  • 1 k:反转第 \(k\) 条边的颜色。
  • 2 u v:询问能否只走黑色的边从 \(u\) 走到 \(v\)

\(n\le 5\times 10^4,1\le m,q\le 10^5\)。没有自环。

时限 \(\text{8.00s}\)

做法解析

观察到原问题显然不弱于 \(\texttt{DAG}\) 可达性,而 \(\texttt{DAG}\) 可达性是 \(O(\frac{n^2}{\omega})\) 的。所以我们大概也得想某种类似复杂度的东西。

没有修改是好做的,bitset 维护可达性即可。有修改呢?这种一眼看上去就是 \(\text{polylog}\) 干不了的图论操作问题,直接考虑操作分块。

设我们 \(B\) 个操作分一块。对于这些操作中被询问或有边被修改的点,我们令其为关键点。显然,在这 \(B\) 个操作中,不通过关键点的,非关键点之间的连通性状况是不变的。所以你可以先用一个 \(tarjan\) 加缩点处理一下这些分量的可达性。然后你再维护一下在不考虑带修边时关键点两两间的连通性。

然后你再一个一个操作来维护即可,修改就改几个信息,查询就一个经 bitset 优化的 \(\texttt{bfs}\),详见代码实现。

最后总的下来时间复杂度是 \(O(\frac{q}{B}\times(\frac{B(n+m)}{w}+\frac{B^2}{w}))\)。自己调一下块长发现 \(B=400\) 时表现较好。

代码实现

分拆讲解

讲解一下程序的主要部分:void solve(int ql,int qr){}

for(int i=ql;i<=qr;i++){auto add=[&](int u)->void {if(cid[u]==-1)kpos[kcnt]=u,cid[u]=kcnt++;};if(C[i].o==1)add(E[C[i].x].u),add(E[C[i].x].v),che[C[i].x]=true;if(C[i].o==2)add(C[i].x),add(C[i].y);
}

这一段是在加入所有关键点、标记关键边。

for(int i=1;i<=M;i++)if(vld[i]&&!che[i])Gr1[E[i].u].push_back(E[i].v);
for(int i=1;i<=N;i++)if(!dfn[i])tarjan(i);
for(int u=1;u<=N;u++)for(int v : Gr1[u])if(bel[u]!=bel[v])Gr2[bel[u]].push_back(bel[v]);

这一段是对在这段操作序列分块中没有修改的边跑 tarjan 并建立缩点后的新图。

for(int i=0;i<kcnt;i++)F[bel[kpos[i]]][i]=1;
for(int u=1;u<=ccnt;u++)for(int v : Gr2[u])F[u]|=F[v];
for(int x=0;x<kcnt;x++)for(int y=0;y<kcnt;y++)T1[x][y]=F[bel[kpos[x]]][y];
for(int i=1,x,y;i<=M;i++)if(vld[i]&&che[i])x=cid[E[i].u],y=cid[E[i].v],ens[x][y]++,T2[x][y]=1;

这里是在处理连通性:

  • F[x][y] 表示分量 \(x\) 到关键点 \(y\) 的连通性。处理它就是一个复杂度瓶颈:\(O(\frac{B(n+m)}{w})\) 每操作序列分块。
  • T1[x][y] 表示关键点 \(x\) 到关键点 \(y\) 的连通性,ens[x][y] 表示在当前操作序列块内,有多少条当前为黑的边直接连通了 \(x\)\(y\),之后 \(\texttt{dfs}\) 就要用到这个转移可达性。T2[x][y] 就是 ens[x][y] 的布尔版本。
for(int i=ql,x,y;i<=qr;i++){auto [co,cx,cy]=C[i];if(co==1){vld[cx]^=1,x=cid[E[cx].u],y=cid[E[cx].v];ens[x][y]+=(vld[cx]?1:-1),T2[x][y]=ens[x][y];}if(co==2){vis.set(),vis[cid[cx]]=0;queue<int> q;q.push(cid[cx]);while(!q.empty()){int u=q.front();q.pop();auto tmp=vis&(T1[u]|T2[u]);for(int v=tmp._Find_first();v<kcnt;v=tmp._Find_next(v))vis[v]=0,q.push(v);}ans[i]=!vis[cid[cy]];}
}
aftinit(ql,qr);

修改操作的代码很易懂。重点讲下查询。

显然这个查询是一个 \(\texttt{bfs}\),虽然它和一般的 \(\texttt{bfs}\) 有点区别。我们发现这次我们的 vis 数组初值居然是全 \(1\) 而非全 \(0\)

这是因为我们要利用 bitset 优化 \(\texttt{bfs}\)。怎么优化?bitset 有找到第一个值为 \(1\) 下标的方法 ._Find_first() 和找到下标 \(x\) 后下一个值为 \(1\) 的下标的方法 ._Find_next(x)。因为 bitset 在查找时是每 \(w\) 位一比较,所以它快。因此,我们要让可达为 \(1\),不可达为 \(0\)

这玩意实际上就类似于“邻接矩阵”。做就完了。最后记得该清空的东西清空。

完整代码

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=5e4+5,MaxM=1e5+5,Ksiz=4e2+5,KK=Ksiz<<1;
int N,M,Q,X,Y,Z,Opt,B=400,ans[MaxM];
struct edge{int u,v;}E[MaxM];
int vld[MaxM],che[MaxM];
struct quer{int o,x,y;}C[MaxM];
int dfn[MaxN],low[MaxN],ntot,ccnt;
int stk[MaxN],ktp,bel[MaxN];
vector<int> Gr1[MaxN],Gr2[MaxN];
void tarjan(int u){dfn[u]=low[u]=++ntot,stk[++ktp]=u;for(int v : Gr1[u]){if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);else if(!bel[v])low[u]=min(low[u],dfn[v]);}if(dfn[u]==low[u]){ccnt++;for(int x=0;x!=u;ktp--)x=stk[ktp],bel[x]=ccnt;}
}
int cid[MaxN],kcnt,kpos[KK],ens[KK][KK];
bitset<KK> vis,F[MaxN],T1[KK],T2[KK];
void aftinit(int ql,int qr){for(int i=ql;i<=qr;i++)if(C[i].o==1)che[C[i].x]=false;for(int i=1;i<=ccnt;i++)F[i].reset(),Gr2[i].clear();for(int i=0;i<kcnt;i++)T1[i].reset(),fill(ens[i],ens[i]+kcnt,0),T2[i].reset(),cid[kpos[i]]=-1;kcnt=ntot=ccnt=0;for(int i=1;i<=N;i++)Gr1[i].clear(),dfn[i]=low[i]=bel[i]=0;
}
void solve(int ql,int qr){for(int i=ql;i<=qr;i++){auto add=[&](int u)->void {if(cid[u]==-1)kpos[kcnt]=u,cid[u]=kcnt++;};if(C[i].o==1)add(E[C[i].x].u),add(E[C[i].x].v),che[C[i].x]=true;if(C[i].o==2)add(C[i].x),add(C[i].y);}for(int i=1;i<=M;i++)if(vld[i]&&!che[i])Gr1[E[i].u].push_back(E[i].v);for(int i=1;i<=N;i++)if(!dfn[i])tarjan(i);for(int u=1;u<=N;u++)for(int v : Gr1[u])if(bel[u]!=bel[v])Gr2[bel[u]].push_back(bel[v]);for(int i=0;i<kcnt;i++)F[bel[kpos[i]]][i]=1;for(int u=1;u<=ccnt;u++)for(int v : Gr2[u])F[u]|=F[v];for(int x=0;x<kcnt;x++)for(int y=0;y<kcnt;y++)T1[x][y]=F[bel[kpos[x]]][y];for(int i=1,x,y;i<=M;i++)if(vld[i]&&che[i])x=cid[E[i].u],y=cid[E[i].v],ens[x][y]++,T2[x][y]=1;for(int i=ql,x,y;i<=qr;i++){auto [co,cx,cy]=C[i];if(co==1){vld[cx]^=1,x=cid[E[cx].u],y=cid[E[cx].v];ens[x][y]+=(vld[cx]?1:-1),T2[x][y]=ens[x][y];}if(co==2){vis.set(),vis[cid[cx]]=0;queue<int> q;q.push(cid[cx]);while(!q.empty()){int u=q.front();q.pop();auto tmp=vis&(T1[u]|T2[u]);for(int v=tmp._Find_first();v<kcnt;v=tmp._Find_next(v))vis[v]=0,q.push(v);}ans[i]=!vis[cid[cy]];}}aftinit(ql,qr);
}
int main(){readis(N,M,Q);for(int i=1;i<=M;i++)readis(E[i].u,E[i].v);fill(vld+1,vld+M+1,1);for(int i=1;i<=Q;i++){readis(Opt,X);if(Opt==2)readi(Y);C[i]={Opt,X,Y};}fill(cid+1,cid+N+1,-1);for(int i=1;i<=Q;i+=B)solve(i,min(i+B-1,Q));for(int i=1;i<=Q;i++)if(C[i].o==2)puts(ans[i]?"YES":"NO");return 0;
}

后记

这个后记是干嘛的。

http://www.sczhlp.com/news/9490/

相关文章:

  • 逻辑计算题
  • AT ARC191E Unfair Game
  • C#中简单Socket编程
  • Markdown博客格式
  • 普科科技PKC7500在电力系统稳定性测试项目中的应用案例
  • clickhouse单机部署命令
  • 第二届智能计算与数据分析国际学术会议(ICDA 2025)
  • MATLAB中的坐标转换功能
  • C# Avalonia 09- CustomControlWithCommand
  • 免费看片!一个开箱即用的、跨平台的影视聚合播放器!
  • 上线仅一个月,免费拼图工具收到赞助啦 - ops
  • docx中图片浅色
  • 感觉人生和王者一样
  • 做软件就能赚钱?
  • 泰森多边形(Voronoi图)生成与网格密度调整
  • 基于Java+Springboot+Vue开发的在线音乐播放推荐系统(前后端分离)
  • 千万大表分区办法
  • 解决Spring Boot中的 java.lang.ClassNotFoundException: dm.jdbc.driver.DmDriver问题
  • AT ARC202D King
  • MySQL事务原理:从ACID到隔离级别的全解析
  • AI编程:代码多,效果好?
  • 为什么你应该学习编程 - 5大优势(附入门指南)
  • 基于AI的课程内容生成系统技术解析
  • ​鸿蒙APMS:开箱即用,崩溃卡顿耗电秒级捕捉
  • 美丽而脆弱的天体运动:当C#遇见宇宙混沌
  • day20
  • 题解:qoj9698 Twenty-two
  • 博文申明
  • 继电保护基本原理
  • 数据仓库命名规范 - 指南