LGP11831_1 [UPTS 2025] 追忆 学习笔记
Luogu Link
前言
又幻想了……唉!
幻想自己场切这道题,最后标准分上升至 \(\text{598pts}\),翻掉了 \(\text{yyz}\),不至于一点脸不要。
本题解基本借鉴这篇题解。
题意简述
给定一个 \(n\) 点 \(m\) 边的简单有向图 \(G\),有 \(m\) 条形如 \((u_i,v_i)\) 的边,其中 \(u_i<v_i\)。每个结点 \(u\) 有两个权值 \(a_u,b_u\),保证 \(\{a_1,\dots,a_n\}\) 和 \(\{b_1,\dots,b_n\}\) 各为一个 \(1\sim n\) 的排列。
给定 \(q\) 次操作,操作分为三种类型:
- 交换 \(a_u\) 和 \(a_v\) 的值。
- 交换 \(b_u\) 和 \(b_v\) 的值。
- 给定 \(u,l,r\),求出满足 \(l\le a_v\le r\) 且存在 \(u\to v\) 路径的所有点 \(v\) 中,\(b_v\) 的最大值。若不存在 \(v\) 满足条件则输出 \(0\)。
请注意本题特别的时空限制:
- 时限 \(\text{9.00s}\)。
- 空限 \(\text{2.00GB}\)。
做法解析
首先,“对于所有边均有 \(u_i<v_i\)”说明 \(G\) 是一个 DAG。连这都没看出来的这辈子有了。
询问操作有两个限制,其中“存在 \(u\to v\) 路径”等价于问任意两点可达性了。而这又是个 DAG,所以我们只能也必然要用 bitset 预处理这一信息,方法见代码。这一步是 \(O(\frac{n^2}{w})\) 的。不然题目给你这么大的时空干嘛??????而且这是经典套路!!!
可达性的限制问题解决了,然后呢?\(a_v\) 值域的限制怎么处理?
你想,既然你都已经用 bitset 搞掉一个限制了,你就不倾向于也用 bitset 搞掉另一个限制么?最后两个限制与一下就可以得到完整限制了。
考虑怎么用 bitset 维护 \(a\) 的限制。
我们首先发现,\(\{i\mid a_i\in [l,r]\}\) 的 bitset 明显可以由 \(\{i\mid a_i\ge l\}\) 异或 \(\{i\mid a_i\ge r+1\}\) 得到。考虑到 \(a\) 带修,我们感性理解交换元素不太能用 \(\log\) 做,考虑根号。令块长 \(S=\sqrt{n}\),分块维护根号个 bitset \(A_x=\{i\mid a_i\ge lim_x\}\)。单次修改显然可以 \(\sqrt{n}\) 做,单次询问也是 \(O(\sqrt(n))\) 的。
此时我们对于询问就能得到一个在两个限制下都合法的答案集合 \(C\) 了。我们现在要求的就是 \(\max_{i\in C}b_i\)。
你想,既然你都已经用 bitset 把满足限制的点集合搞出来了,你就不倾向于也类似地维护根号个 bitset 依次表示 \(B_u\) 的值在某个区间的点集合吗?最后根号地贪心扫一下然后再 bitset 内扫一下反正差不多不就做完了?
具体来说,维护 \(\sqrt{n}\) 个 bitset \(B_x=\{i\mid k_x\le b_i<k_{x+1}\}\)。然后倒着把 \(B\) 扫一遍,找到第一个 \(B_x\) 满足 \(C\oplus B_x\neq 0\),然后再在块内找……吗?不太行,因为整个 bitset 之间的比较复杂度很高,这样的单次询问复杂度是 \(O(\frac{n}{w}\sqrt{n})\) 的,直接爆了。
但是如果搞个后缀和,就可以秒变 \(O(\frac{n}{w}+\sqrt{n})\) 了。
原理如图。
好了这题就做完了。
你想,你都已经用 bitset 在赛后把这道题 AC 了,你就不倾向于在赛场上把这题正解写了吗?当时你干嘛去了?
代码实现
略难于 LGP2801,神奇吧,联合省选。
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
using namespace obicet;
const int MaxN=1e5+5,MaxNr=3.5e2;
int N,M,Q,X,Y,Opt,Z;
vector<int> Gr[MaxN];
void addedge(int u,int v){Gr[u].push_back(v);}
int ksiz,knum,lb[MaxNr],rb[MaxNr],bel[MaxN];
int A[MaxN],na[MaxN],B[MaxN],nb[MaxN],ans;
bicet sg[MaxN],sa[MaxNr],sb[MaxNr],sc;
void befinit(int n){for(int i=1;i<=n;i++)Gr[i].clear(),sg[i].reset();ksiz=sqrt(N),knum=pcedi(N,ksiz);for(int i=1;i<=knum;i++)sa[i].reset(),sb[i].reset();
}
void mian(){readi(N),readi(M),readi(Q),befinit(N);for(int i=1;i<=N;i++)Gr[i].clear();for(int i=1;i<=M;i++)readi(X),readi(Y),addedge(X,Y);for(int i=N;i>=1;i--){sg[i].set(i);for(int v : Gr[i])sg[i]|=sg[v];}for(int i=1;i<=knum;i++)lb[i]=rb[i-1]+1,rb[i]=ksiz*i;rb[knum]=N;for(int i=1;i<=N;i++)bel[i]=pcedi(i,ksiz);for(int i=1;i<=N;i++)readi(A[i]),na[A[i]]=i;for(int i=1;i<=N;i++)readi(B[i]),nb[B[i]]=i;for(int i=1;i<=N;i++)sa[bel[A[i]]].set(i),sb[bel[B[i]]].set(i);for(int i=knum-1;i;i--)sa[i]|=sa[i+1],sb[i]|=sb[i+1];while(Q--){readi(Opt);if(Opt==1){readi(X),readi(Y);int kl=bel[A[X]],kr=bel[A[Y]];if(kl>kr)swap(kl,kr);for(int i=kl+1;i<=kr;i++)sa[i].flip(X),sa[i].flip(Y);swap(A[X],A[Y]),swap(na[A[X]],na[A[Y]]);}if(Opt==2){readi(X),readi(Y);int kl=bel[B[X]],kr=bel[B[Y]];if(kl>kr)swap(kl,kr);for(int i=kl+1;i<=kr;i++)sb[i].flip(X),sb[i].flip(Y);swap(B[X],B[Y]),swap(nb[B[X]],nb[B[Y]]);}if(Opt==3){readi(Z),readi(X),readi(Y);ans=0,sc=sa[bel[X]],sc^=sa[bel[Y]+1];for(int i=Y+1;i<=rb[bel[Y]];i++)sc.flip(na[i]);for(int i=lb[bel[X]];i<X;i++)sc.flip(na[i]);sc&=sg[Z];int k=1;for(int i=0;i<bicet::Siz&&k<knum;i++)while(sc.t[i]&sb[k+1].t[i])k++;for(int i=rb[k];i>=lb[k];i--)if(sc.val(nb[i])){ans=i;break;}writi(ans),puts("");}}
}
int Tpn,Tcn;
int main(){readi(Tpn),readi(Tcn);while(Tcn--)mian();return 0;
}
反思总结
经典套路:用 bitset \(O(\frac{n^2}{w})\) 预处理 DAG 任意两点可达性。
一个限制能用 bitset,试试把别的限制也用 bitset 干掉,可以考虑分块。
当感觉一个东西可以维护但是复杂度暂时不对时,画画图,搞搞缀和差分,没准就可以了。
后记
暴力可过,怎么回事呢?
你说的对,但是 QOJ 追忆 [-365]
。
形不成形,意不在意,再回去练练吧!