[线段树] P8375 [APIO2022] 游戏

posted on 2025-05-12 08:07:47 | under | source

题意:\(n\) 个点的图,\(0\dots k-1\) 是特殊点。初始时只有一条 \(\forall i<k-1,i\to i+1\) 的链,接下来逐步加入 \(m\) 条有向边,每次判断是否存在一个环,满足环上存在特殊点。\(k\le n\le 3\times 10^5,m\le 5\times 10^5\)

每次找环是困难的,考虑利用初始链的性质,得到一种便于判定的方法。容易发现,在初始链上,可以到达 \(u\) 的点(前驱)构成一段前缀,\(u\) 可以到达的点(后继)构成一段后缀。

所以记 \(pre_i,suf_i\) 为在 \(0\dots k-1\) 的范围内的前缀、后缀,初始时非特殊点 \(pre_i=-1,suf_i=k\),特殊点 \(pre_i=suf_i=i\)

对于不含非特殊点的环,只需判定是否存在一条两端为特殊点且往回走的边即可(而不往回走又没有意义,所以接下来忽略两端为特殊点的边);否则,有环当且仅当存在非特殊点满足 \(pre_i\ge suf_i\)

直接暴力维护 \(pre,suf\) 可以做到 \(O((n+m)k)\)

准确维护的方法貌似只有暴力,所以破产了。那么考虑放宽限制,但保证正确性。具体来说,将 \([pre_i,suf_i]\) 视为一个区间,然后对 \(-1\dots k\) 建线段树,将 \([pre_i,suf_i]\) 下放到线段树节点上。我们声称,将 \([pre_i,suf_i]\) 视为线段树上的区间,不会影响正确性。

还有一个好处,非特殊点若到达叶子节点就说明有环,所以保证了时间复杂度。

接下来考虑怎么维护线段树。首先我们声称:经过修改,若线段树区间未发生变化,则不会产生环,也就是将线段树区间视为实际区间不影响正确性。

分讨:

  • \(x,y\) 的线段树区间相同:并无影响。
  • \(x,y\) 的线段树区间相离:若 \(x\) 区间在 \(y\) 区间左边,区间不变直接忽略;若 \(x\)\(y\) 右边,则会产生一个环,包含 \([suf_y,pre_x]\) 中的特殊点。
  • \(x,y\) 的线段树区间包含:根据 \(x,y\) 的祖先后代关系、在左右哪个儿子子树进行分讨,发现只有两种情况线段树区间发生变动。以 \(y\)\(x\) 的左儿子子树为例,那么影响是 \(suf_x=suf_y\),我们先把 \(x\) 下放到左儿子继续递归即可。\(x\)\(y\) 的右儿子同理。
  • 此外,若线段树区间发生变化,则应当更新所有与它相连的边。

关于正确性:是否可能存在一条边 \(u\to v\),满足 \(u\)\(v\) 的右儿子子树,一开始不会产生环,但后来 \(v\) 区间缩小,导致虽然它们的线段树区间没变,可是实际区间 \(u\)\(v\) 相离且 \(u\) 在右边,然后产生环?并不会,因为一开始处理这条边时就已经搞好了会判出环。其它情况同理。

复杂度均摊 \(O((n+m)\log k)\)

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 3e5 + 5;
int n, m, k, l[N], r[N];
vector<int> to[N], to2[N];

void init(int nn, int kk) {
    n = nn, k = kk;
    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; 
}
inline bool upd_edge(int x, int y);
inline bool upd_node(int x){
    for(auto y : to[x])
        if(upd_edge(x, y)) return true;
    for(auto y : to2[x])
        if(upd_edge(y, x)) return true;
    return false; 
}
inline bool upd_edge(int x, int y){
    if(x < k && y < k) return x >= y;
    if(r[x] < l[y]) return false;
    if(r[y] < l[x]) return true;
    if(l[x] == l[y] && r[x] == r[y]) return false;
    int midx = (l[x] + r[x]) >> 1, midy = (l[y] + r[y]) >> 1;
    if(r[y] <= midx){
        r[x] = midx;
        if(l[x] == r[x]) return true;
        return upd_node(x); 
    }
    if(l[x] > midy){
        l[y] = midy + 1;
        if(l[y] == r[y]) return true;
        return upd_node(y);
    }
    return false;
}
int add_teleporter(int u, int v) {
    to[u].push_back(v), to2[v].push_back(u);
    return upd_edge(u, v);
}
inline void print(){
    for(int i = 0; i < n; ++i) cout << l[i] << ' ' << r[i] << endl;
    cout << endl;
}
posted @ 2026-01-13 11:21  Zwi  阅读(0)  评论(0)    收藏  举报