线段树分治

作用

处理一些加边删边问题。

实现

不知道为啥叫线段树分治,不如叫时间线段树。

如果一条边 \(e\)\(l\) 时刻加入 \(r\) 时刻删除,那么可以看成在 \([l,r)\) 的时刻内都有这条边。

考虑用线段树维护时刻,给线段树上每个区间维护一个 vector,里面存覆盖了这个区间的边。

然后询问时就在线段树上递归,每次把这个区间的边加进一个数据结构中,当离开这个区间时,把这个区间中的边给撤销。

所以维护边的数据结构要支持撤销,一般使用可撤销并查集。

题目

【模板】线段树分治

首先先把题目的 \(l\) 时刻出现,\(r\) 时刻删除变成在 \([l+1,r]\) 时刻内出现。

考虑如何快速判定二分图,可以考虑扩展域并查集,也就是维护两个集合 \(S\)\(T\),每次添加一条边 \((u,v)\),若这两个点属于相同的集合,那么就不是二分图。

有了这个,对时间开一棵线段树,再用可撤销并查集维护边即可。

代码
#include <bits/stdc++.h>

void Freopen() {
    freopen("", "r", stdin);
    freopen("", "w", stdout);
}

using namespace std;
const int N = 2e5 + 10, M = 2e5 + 10, inf = 1e9, mod = 998244353;

int n, m, K;

struct DSU {
    int fa[N], siz[N], n;
    vector< pair< int, int> > del;

    DSU() {}

    DSU( int _n) {
        n = _n; del.clear();
        for ( int i = 1; i <= n; i ++) siz[i] = 1, fa[i] = i;
    }

    int find( int x) {
        return x == fa[x] ? x : find(fa[x]);
    }

    int merge( int u, int v) {
        u = find(u), v = find(v);
        if (u == v) return 0;

        if (siz[u] < siz[v]) swap(u, v);
        del.push_back({u, v});
        siz[u] += siz[v], fa[v] = u;
        return 1;
    }

    void Del() {
        if (! del.size()) return ;
        auto [u, v] = del.back(); del.pop_back();
        siz[u] -= siz[v], fa[v] = v;
    }
} D;

vector< pair< int, int> > tr[N * 4];

void ins( int x, int y, pair< int, int> e, int k = 1, int l = 1, int r = K) {
    if (x > y) return ;
    if (x <= l && r <= y) return tr[k].push_back(e), void();

    int mid = (l + r) >> 1;

    if (x <= mid) ins(x, y, e, k << 1, l, mid);
    if (y > mid) ins(x, y, e, k << 1 | 1, mid + 1, r);
}

void solve( int k = 1, int l = 1, int r = K) {
    int tot = 0; // tot 表示这个区间加了多少边

    for ( auto [u, v] : tr[k]) {
        if (D.find(u) == D.find(v)) {
            for ( int i = l; i <= r; i ++)
                cout << "No\n";

            while (tot --) D.Del();
            return ;
        }

        tot += D.merge(u, v + n);
        tot += D.merge(u + n, v);
    }

    if (l == r) {
        cout << "Yes\n";

        while (tot --) D.Del();
        return ;
    }

    int mid = (l + r) >> 1;
    solve(k << 1, l, mid), solve(k << 1 | 1, mid + 1, r);

    while (tot --) D.Del();
}

signed main() {
    ios :: sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m >> K;
    D = DSU(2 * n);

    for ( int i = 1; i <= m; i ++) {
        int x, y, l, r;
        cin >> x >> y >> l >> r;
        l ++;

        ins(l, r, make_pair(x, y));
    }

    solve();

    return 0;
}

[NOI2014] 魔法森林

要让两个值的最大值之和最小,不好做。

考虑直接二分和,记为 \(lim\)

那么就是求出一条 \(1\to n\) 的路径,使得 \(\max a_i+\max b_i\le lim\),记 \(c_i=lim-b_i\),那么就有 \(\max a_i\le \min c_i\)

那么,也就是要找到一个 \(z\),使得每条路径都满足 \(a_i\le z\le c_i\)

考虑线段树分治做这个,在 \([a_i,lim-b_i]\) 加入 \((u_i,v_i)\) 这条边,然后判断是否有一个位置使得 \(1\)\(n\) 连通。

复杂度 \(O(n\log^3 n)\),但由于二分、并查集、分治的常数都很小,随便过。

代码
#include <bits/stdc++.h>

void Freopen() {
    freopen("", "r", stdin);
    freopen("", "w", stdout);
}

using namespace std;
const int N = 2e5 + 10, M = 2e5 + 10, inf = 1e9, mod = 998244353;

int n, m;

struct edge {
    int u, v, a, b;
} E[N];

struct DSU {
    int fa[N], siz[N], n;
    vector< pair< int, int> > del;

    DSU() {}

    DSU( int _n) {
        n = _n; del.clear();
        for ( int i = 1; i <= n; i ++) siz[i] = 1, fa[i] = i;
    }

    int find( int x) {
        return x == fa[x] ? x : find(fa[x]);
    }

    int merge( int u, int v) {
        u = find(u), v = find(v);
        if (u == v) return 0;

        if (siz[u] < siz[v]) swap(u, v);
        del.push_back({u, v});
        siz[u] += siz[v], fa[v] = u;
        return 1;
    }

    void Del() {
        if (! del.size()) return ;
        auto [u, v] = del.back(); del.pop_back();
        siz[u] -= siz[v], fa[v] = v;
    }
} D;

vector< pair< int, int> > tr[N * 4];

void ins( int x, int y, pair< int, int> e, int k = 1, int l = 1, int r = 100000) {
    if (x > y) return ;
    if (x <= l && r <= y) return tr[k].push_back(e), void();

    int mid = (l + r) >> 1;

    if (x <= mid) ins(x, y, e, k << 1, l, mid);
    if (y > mid) ins(x, y, e, k << 1 | 1, mid + 1, r);
}

void del( int x, int y, int k = 1, int l = 1, int r = 100000) {
    if (x > y) return ;
    if (x <= l && r <= y) return tr[k].pop_back(), void();

    int mid = (l + r) >> 1;

    if (x <= mid) del(x, y, k << 1, l, mid);
    if (y > mid) del(x, y, k << 1 | 1, mid + 1, r);    
}

int solve( int k = 1, int l = 1, int r = 100000) {
    int tot = 0;
    for ( auto [u, v] : tr[k]) tot += D.merge(u, v);

    if (D.find(1) == D.find(n)) {
        while (tot) D.Del(), tot --;
        return 1;
    }

    if (l == r) {
        while (tot) D.Del(), tot --;
        return 0;
    }

    int mid = (l + r) >> 1;

    int F = 0;
    F |= solve(k << 1, l, mid);
    if (F) {
        while (tot) D.Del(), tot --;
        return 1;
    }
    F |= solve(k << 1 | 1, mid + 1, r);
    while (tot) D.Del(), tot --;
    return F;
}

int chk( int lim) {
    for ( int i = 1; i <= m; i ++)
        ins(E[i].a, lim - E[i].b, make_pair(E[i].u, E[i].v));

    int res = solve();

    for ( int i = 1; i <= m; i ++)
        del(E[i].a, lim - E[i].b);

    return res;
}

signed main() {
    ios :: sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    cin >> n >> m;
    for ( int i = 1; i <= m; i ++) {
        int u, v, a, b; cin >> u >> v >> a >> b;
        E[i] = {u, v, a, b};
    }

    D = DSU(n);

    int l = 2, r = 100000, ans = -1;

    while (l <= r) {
        int mid = (l + r) >> 1;

        if (chk(mid)) ans = mid, r = mid - 1;
        else l = mid + 1;
    }

    cout << ans << '\n';

    return 0;
}
posted @ 2025-10-11 20:30  咚咚的锵  阅读(17)  评论(0)    收藏  举报