【DSY 4783】小Z的作业 题解 (可撤销并查集 + 线段树分治)

DSY 传送门

可撤销并查集 + 线段树分治。

Question

\(n\)\(m\) 条无向边,边由 \(1\)\(m\) 编号,\(q\) 次询问求这 \(n\) 个点以及编号在 \([l,r]\) 区间内的边组成的图 的连通块个数是否 \(\le\) 某个定值 \(k\)

Solution

0 离线

算是题外话吧,毕竟考场上好不容易想到的。

很显然,就是莫队 + 可撤销并查集。这里有一道弱化离线版,题解附代码实现。

1 在线

回归正题,其实此题的根本思路非常经典的了(见《Tricks 整理》思路部分)。

即,去维护一个 \(f\) 数组,记 \(f_i\) 表示以 \(i\) 为右端点,最小的左端点满足答案为 Yes。这样一来,通过一个预处理求出 \(f\) 数组,可以将强制在线转化为离线倒序处理(这是感性理解的说法)。

然后考虑求 \(f\) 数组。此题是把一个区间内的所有边添加进去,其余边删掉。

不妨看下图,发现灰色部分所涵盖的边是在统计 \(f_i\)\(f_{i-1}\) 的时候都涉及到的。

(1)线段树分治

也即是说,我们需要一个数据结构,去支持撤销操作,或者说是维护一个操作影响的时间区间。

然后线段树分治(也作时间分治)应运而生。

每个叶子看作一条边,如果第 \(x\) 条边对 \([t_i.l,t_i.r]\) 都有贡献/影响,那么直接在 \(i\) 处把 \(x\) 记录一下,经过时加上,这样在遍历后续儿子们的时候这条边就一直存在。最后 return; 前使用可撤销并查集删掉即可。

具体如何判断第 \(x\) 边对何区间有影响:

  • 考虑先求出 \(f_n\),直接从第 \(n\) 条边开始按编号递减顺序加入并判断即可,由于 \(∀i<n,f_i≤f_n\),所以 \(∀i∈[f_n,n]\) ,我们可以直接在 \([i,n]\) 这个区间加入边 \(i\)
  • 接着考虑求出 \(f_{n−1}\),方法类似,从第 \(f_{n−1}\) 条边开始按编号递减顺序加入并判断即可。\(∀i∈[f_{n-1},f_n]\),我们依然可以直接在 \([i,n-1]\) 这个区间加入边 \(i\)

(2)LCT

其实我们还发现,每次需要删除的边只有第 \(i\) 条而已,动态加边删边,自然就想到是 LCT 了。

使用 LCT,线性倒序扫描,每次判断删掉第 \(i\) 条边之后对连通块个数是否有影响即可。

这个做法写法都很显然了,就不再赘述了。

Code

(1)线段树分治

#include<bits/stdc++.h>
using namespace std;
  
#define rep(i, a, b) for(register int i = a; i <= b; ++i)
const int maxn = 2e5 + 5;
int n, m, k, tp, q;
int cnt, fa[maxn], sz[maxn];
int st[maxn], top;
struct node{
    int u, v;
}e[maxn];
vector <node> s[1 << 20];
int f[maxn]; unsigned lst = 0;
  
inline int fnd(int x){
    return fa[x] == x ? x : fnd(fa[x]);
}
inline void lnk(int x, int y){
    x = fnd(x), y = fnd(y);
    if(x == y) return;
    if(sz[x] > sz[y]) swap(x, y);
    st[++top] = x, cnt -= 1, fa[x] = y, sz[y] += sz[x];
}
inline void undo(){
    int x = st[top--]; cnt += 1; 
    sz[fa[x]] -= sz[x], fa[x] = x;
}
  
inline void updt(int i, int l, int r, int L, int R){
    if(L <= l and r <= R){
        s[i].push_back(e[L]); return;
    } int mid = l + r >> 1;
    if(mid >= L) updt(i << 1, l, mid, L, R);
    if(mid < R) updt(i << 1 | 1, mid + 1, r, L, R);
}
inline void build(int i, int l, int r){
    int init = top; for(auto nw : s[i]) lnk(nw.u, nw.v);
    if(l == r){
        f[l] = f[l + 1];
        while(f[l] > 1){ if(cnt <= k) break;
            lnk(e[f[l] - 1].u, e[f[l] - 1].v);
            if(cnt <= k) break;
            f[l] -= 1; if(f[l] < l) updt(1, 1, m, f[l], l - 1); 
        } while(top > init) undo(); return;
    } int mid = l + r >> 1;
    build(i << 1 | 1, mid + 1, r), build(i << 1, l, mid);
    while(top > init) undo();
}
  
int main(){
    scanf("%d%d%d%d", &n, &m, &k, &tp);
    rep(i, 1, m) scanf("%d%d", &e[i].u, &e[i].v);
    rep(i, 1, n) fa[i] = i, sz[i] = 1; 
    cnt = n, f[m + 1] = m + 1, build(1, 1, m);
    scanf("%d", &q);
    rep(i, 1, q){ int l, r;
        scanf("%d%d", &l, &r);
        if(tp) l = (l + lst) % m + 1, r = (r + lst) % m + 1;
        if(l > r) swap(l, r); lst <<= 1;
        if(f[r] <= l) printf("No\n"); else printf("Yes\n"), lst += 1;
    }
    return 0;
}

(2)LCT

Code by @kymru

#include <cstdio>
#include <set>
using namespace std;
 
const int maxn = 2e5 + 5;
const int maxm = 2e5 + 5;
const int nd_sz = 4e5 + 5;
 
struct link_cut_tree
{
    int top, q[nd_sz];
    int fa[nd_sz], son[nd_sz][2], idx[nd_sz], val[nd_sz];
    bool rev[nd_sz];
 
    void push_up(int x) { val[x] = max(max(val[son[x][0]], val[son[x][1]]), idx[x]); }
 
    void push_down(int x)
    {
        if (rev[x])
        {
            rev[son[x][0]] ^= 1;
            rev[son[x][1]] ^= 1;
            rev[x] = false;
            swap(son[x][0], son[x][1]);
        }
    }
 
    bool get(int x) { return (son[fa[x]][1] == x); }
 
    bool is_root(int x) { return (son[fa[x]][0] != x) && (son[fa[x]][1] != x); }
 
    void rotate(int x)
    {
        int y = fa[x], z = fa[y], k = get(x);
        son[y][k] = son[x][k ^ 1];
        fa[son[x][k ^ 1]] = y;
        son[x][k ^ 1] = y;
        if (!is_root(y)) son[z][son[z][1] == y] = x;
        fa[x] = z, fa[y] = x;
        push_up(y), push_up(x);
    }
 
    void splay(int x)
    {
        q[top = 1] = x;
        for (int i = x; !is_root(i); i = fa[i]) q[++top] = fa[i];
        for (int i = top; i; i--) push_down(q[i]);
        while (!is_root(x))
        {
            int y = fa[x], z = fa[y];
            if (!is_root(y)) rotate(get(x) == get(y) ? y : x);
            rotate(x);
        }
    }
 
    void access(int x)
    {
        for (int t = 0; x; t = x, x = fa[x])
        {
            splay(x);
            son[x][1] = t;
            push_up(x);
        }
    }
 
    void make_root(int x)
    {
        access(x);
        splay(x);
        rev[x] ^= 1;
    }
 
    int find(int x)
    {
        access(x);
        splay(x);
        while (son[x][0]) x = son[x][0];
        return x;
    }
 
    void split(int x, int y)
    {
        make_root(x);
        access(y);
        splay(y);
    }
 
    void cut(int x, int y)
    {
        split(x, y);
        if ((son[y][0] == x) && (son[x][1] == 0)) son[y][0] = fa[x] = 0;
    }
 
    void link(int x, int y)
    {
        make_root(x);
        fa[x] = y;
    }
} tree;
 
int n, m, k, tp, q;
int ui[maxm], vi[maxm], rgt[maxm];
set<int> idx;
 
int main()
{
    unsigned last_ans = 0;
    scanf("%d%d%d%d", &n, &m, &k, &tp);
    k = n - k;
    for (int i = 1; i <= m; i++) scanf("%d%d", &ui[i], &vi[i]);
    for (int i = 1; i <= n + m; i++) tree.idx[i] = tree.val[i] = i;
    for (int i = m; i >= 1; i--)
    {
        if (ui[i] != vi[i])
        {
            if (tree.find(ui[i]) == tree.find(vi[i]))
            {
                tree.split(ui[i], vi[i]);
                int pos = tree.val[vi[i]];
                tree.cut(ui[pos - n], pos);
                tree.cut(pos, vi[pos - n]);
                idx.erase(pos - n);
            }
            tree.link(ui[i], n + i);
            tree.link(n + i, vi[i]);
            idx.insert(i);
        }
        if (idx.size() > k)
        {
            int pos = (*(--idx.end()));
            idx.erase(pos);
            tree.cut(ui[pos], n + pos);
            tree.cut(n + pos, vi[pos]);
        }
        if (idx.size()) rgt[i] = (idx.size() == k ? (*(--idx.end())) : m + 1);
        else rgt[i] = (k ? m + 1 : 0);
    }
    scanf("%d", &q);
    while (q--)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        if (tp == 1)
        {
            l = (l + last_ans) % m + 1;
            r = (r + last_ans) % m + 1;
            if (l > r) swap(l, r);
        }
        puts(r >= rgt[l] ? "Yes" : "No");
        last_ans <<= 1;
        if (r >= rgt[l]) last_ans++;
    }
    return 0;
}

感谢阅读。

posted @ 2022-10-05 17:44  pldzy  阅读(103)  评论(0)    收藏  举报