Description
Solution
注意到,原问题等价于加边,查询是否存在一个包含前 k k k 个点至少一者(令其为关键点)的环,并强制在线。
算法一
令 L u L_u Lu 表示节点 u u u 可达的编号最大的关键点, R u R_u Ru 表示可达节点 u u u 的编号最小的关键点。那么,答案为 1 1 1 当且仅当存在 u u u 使 L u ≥ R u L_u \ge R_u Lu≥Ru。
每当加入一条边 ( u , v ) (u,v) (u,v) 后,我们从 u u u 出发往外 dfs,不断尝试用 R u R_u Ru 来更新其他的 R R R;同理,我们也从 v v v 在反图上往外 dfs,不断尝试用 L v L_v Lv 来更新其他的 L L L。
一般实现的复杂度是 O ( m ( n + m ) ) O(m(n+m)) O(m(n+m)) 的,但如果我们加一个剪枝:无法更新就返回,那么每走一条边就会使一个节点的 L , R L,R L,R 之一产生变化。而根据势能分析,每个节点的 L , R L,R L,R 的变化次数总和是 O ( n k ) O(nk) O(nk) 的,所以总复杂度其实是 O ( ( n + m ) k ) O((n+m)k) O((n+m)k) 的。
期望得分 60 60 60 分。
算法二
既然关键点数量不多时算法一优秀,那么我们考虑将关键点变少。具体来说,我们分块,对于每个节点 u u u 维护 L u , R u L_u,R_u Lu,Ru 所在的块。当块长为 k \sqrt k k 时,复杂度就是 O ( ( n + m ) k ) O((n+m) \sqrt k) O((n+m)k) 的。
特别的,若 L u , R u L_u,R_u Lu,Ru 属于同一个块,那么暴力更新。由于块长不大,所以这一部分的复杂度也是 O ( ( n + m ) k ) O((n+m) \sqrt k) O((n+m)k) 的。
总时间复杂度 O ( ( n + m ) k ) O((n+m) \sqrt k) O((n+m)k),期望得分 [ 60 , 100 ] [60,100] [60,100] 分。
算法三
首先你要注意到,分块本质上就是线段树的退化版,将线段树的层数压缩到了 2 2 2。然而,本题并不需要依赖分块的特殊结构,于是我们考虑转而用线段树来维护。
具体来说,以 [ − 1 , k ] [-1,k] [−1,k] 为值域建立线段树,将每个点 u u u 记录在极小的包含 [ L u , R u ] [L_u,R_u] [Lu,Ru] 的线段树节点处。我们不断地尝试用下层更新上层的点,使各个节点被记录的位置不断变深。在实现方面,我们并不需要建立线段树,只需对各个 u u u 维护其在虚拟的线段树上对应的区间即可。
由于线段树只有 O ( log k ) O(\log k) O(logk) 层,所以总复杂度 O ( ( n + m ) log k ) O((n+m) \log k) O((n+m)logk),完美解决本题。
Summary
看到本题,非常容易想到维护 L , R L,R L,R。
首先考虑 60 60 60 分怎么做,根据势能分析,发现 dfs 一下的复杂度就是对的。
再考虑 100 100 100 分怎么做。由于关键点的数量较少可以做,那么我们可以分块,这样就能做到较为优秀的 O ( ( n + m ) k ) O((n+m) \sqrt k) O((n+m)k)。
最后发现做法并不依赖分块结构的特殊性,于是转而用线段树大力维护,就能做到完美的 O ( ( n + m ) log k ) O((n+m) \log k) O((n+m)logk) 了。
本题关键在于挖掘出转移的特殊性,并考察了对分块,分治等结构的清楚认识,是一道不可多得的好题。
Code
略微压行,代码很短,只有 663 B。
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,k,l[N],r[N]; vector<int> A[N],B[N];
void init(int _n,int _k){
n=_n,k=_k;
for (int i=0;i<k;i++) l[i]=r[i]=i;
for (int i=k;i<n;i++) l[i]=-1,r[i]=k;
}
bool upd(int u);
bool chk(int u,int v){
if (l[u]>=r[v]) return 1;
int midu=(l[u]+r[u])>>1,midv=(l[v]+r[v])>>1;
if (l[u]>midv) return l[v]=midv+1,upd(v);
if (r[v]<=midu) return r[u]=midu,upd(u);
return 0;
}
bool upd(int u){
for (auto v:A[u]) {if(chk(u,v))return 1;}
for (auto v:B[u]) {if(chk(v,u))return 1;}
return 0;
}
int add_teleporter(int u,int v){A[u].push_back(v),B[v].push_back(u);return chk(u,v);}