首页 > 编程语言 >线段树分治 / 时间分治 算法笔记

线段树分治 / 时间分治 算法笔记

时间:2023-01-21 20:11:50浏览次数:58  
标签:sz int 线段 分治 tim 算法 include

线段树分治

定义

线段树的本质就是 分治的实体化

我理解的狭义上的线段树指的是支持区间查询修改的一种数据结构,而广义上的线段树则是一种思想——分治实体化的思想。

线段树分治就是由这种思想催生出的产物(同样的还有线段树优化建边)。

线段树分治有一个别名叫 时间分治,它的主要思想就是对于一些在线算法比较难写的动态问题中,维护整个动态问题的时间轴,从而离线地高效解决整个问题。

想法

具体而言,如果有这么一个问题
你要维护一张无向简单图。你被要求加入删除一条边及查询两个点是否连通。

  • 0:加入一条边。保证它不存在。
  • 1:删除一条边。保证它存在。
  • 2:查询两个点是否连通。

如果想要在线地解决这个问题比较麻烦(那个L什么什么CT同学,我就不点名了),发现每条边都只存在于某一段区间,我们可以把整个问题的修改操作放到时间轴上。

例如样例2:

image

最后得到的结果为 N Y Y N

我们在时间轴上建立线段树

image

因此对于每一个查询我们只需要考虑这个时间点(叶子)到根的路径上的所有边即可,可以用并查集维护两点连通性。

优化

但是难道我们需要对每个查询都建一个并查集吗?可以观察到,两个查询所构成的图之间是有重复的边的,从这一点入手,我们只需要使用 UNDO 并查集+按轶合并优化 就可以把时间复杂度优化到 \(O(n\log m\log n)\),\(m\) 是时间轴的长度。

实现

// Problem: #121. 「离线可过」动态图连通性
// Contest: LibreOJ
// URL: https://loj.ac/p/121
// Memory Limit: 512 MB
// Time Limit: 800 ms
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2023-01-21 15:28:46

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iostream>
#include <map>
#include <queue>
#include <stack>
#include <tuple>
#include <unordered_map>
#define x first
#define y second
#define speedup (ios::sync_with_stdio(0), cin.tie(0), cout.tie(0))
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int N = 2e6 + 10, M = 2e6 + 10;

int tim = 1;

map<PII, PII> e;
struct qwq
{
    int u, v;
};
vector<qwq> query[N];
int es[N];

int fa[N], sz[N], top;
int stk[N];
void init(int n)
{
    for (int i = 1; i <= n; i++)
        fa[i] = i, sz[i] = 1;
}

inline int find(int x)
{
    while (x != fa[x])
        x = fa[x];
    return x;
}
inline bool merge(int a, int b)
{
    int x = find(a), y = find(b);
    if (x == y)
        return 1;
    if (sz[x] > sz[y])
        swap(x, y);
    fa[x] = y, sz[y] += sz[x];
    stk[++top] = {x};
    return 0;
}

inline void undo()
{
    int t = stk[top--];
    sz[fa[t]] -= sz[t];
    fa[t] = t;
}

inline bool same(int a, int b)
{
    return find(a) == find(b);
}

vector<PII> dat[M];
void update(int k, int l, int r, int ll, int rr, PII x)
{
    if (ll > r || rr < l)
        return;
    if (ll <= l && rr >= r)
    {
        dat[k].push_back(x);
        return;
    }
    int mid = l + r >> 1;
    if(ll <= mid) update(k << 1, l, mid, ll, rr, x);
    if(rr > mid) update(k << 1 | 1, mid + 1, r, ll, rr, x);
}

void solve(int k, int l, int r)
{
    int backup = top;
    for (auto [u, v] : dat[k])
        merge(u, v);
    if (l == r)
    {
        for (auto [u, v] : query[l])
            puts(same(u, v) ? "Y" : "N");
    }
    else
    {
        int mid = l + r >> 1;
        solve(k << 1, l, mid);
        solve(k << 1 | 1, mid + 1, r);
    }
    while (top > backup)
        undo();
}

signed main()
{
    int n, m;
    cin >> n >> m;
    init(n);
    for (int i = 1; i <= m; i++)
    {
        int op, u, v;
        cin >> op >> u >> v;
        if(u > v) swap(u, v);
        if (!op) // 加边
            e[{u, v}] = {++tim, 0};
        else if (op == 1)
            e[{u, v}].y = tim ++;
        else
            query[tim].push_back({u, v});
    }

    for (auto [key, val] : e)
    {
        if(!val.y) val.y = m;
        update(1, 1, tim, val.x, val.y, key);
    }
    solve(1, 1, tim);
    cout << ' ';
    return 0;
    puts("我永远喜欢伊蕾娜~!!");
}

标签:sz,int,线段,分治,tim,算法,include
From: https://www.cnblogs.com/MoyouSayuki/p/17064011.html

相关文章