整体二分 学习笔记

整体二分 学习笔记

引入

假设有这么一道题目:

给定一个整数序列 \(a_1, a_2\dots a_n\),求 \(a_l\sim a_r\) 中第 \(k\) 小的元素。

\(1\le n\leq 10^5, |a_i|\le 10^9\)

这题实际上可以用二分答案求解。

不是二分下标,而是二分值域,对于每一个取值,检验是否有 \(k - 1\) 个数小于它,如果是则缩小值域,否则扩大值域。

整体二分

将这个题目作出扩展。

给定一个整数序列 \(a_1, a_2\dots a_n\),有 \(q\) 次询问,每次询问有 \(l_i, r_i, k_i\), 回答 \(a_{l_i}\sim a_{r_i}\) 中第 \(k_i\) 小的元素。

\(1\le n\leq 10^5,\ 1\le q\le 10^5,\ |a_i|\le 10^9\)

如果对每次询问都在线地回答,时间复杂度会爆掉。

对于这种问题,可以考虑离线地进行整体二分。

之前二分答案的时候只处理了一次询问,这次直接把所有的询问统一处理。

将当前询问集合称为 \(Q\)\(ql\sim qr\) 值域区间元素集合 \(A\)

二分地,设 \(m = \lfloor \dfrac{ql+qr}{2}\rfloor\),于是整个元素集合分为两部分

  • 代表 \([ql, m]\)\(A_1\)
  • 代表 \((m, qr]\)\(A_2\)

接着对 \(Q\) 进行划分,对于一个询问 \((l, r, k)\),注意此处 \(l, r\) 为下标,作出以下分类:

令 满足 \(i\in[l, r](a_i\le m)\)\(i\) 的个数为 \(t\),这个统计问题可以用树状数组在 \(O(\log n)\) 的时间内维护(类似逆序对的解法)。

  1. \(t\ge k\),代表这个问题的答案应该是在 \([ql, m]\) 这个值域内的,放入集合 \(Q_1\)
  2. \(t<k\),代表这个问题的答案应该是在 \([ql, m]\) 这个值域外的,放入集合 \(Q_2\)

这样就完成了集合的划分,把 \(Q_1, A_1\)\(Q_2, A_2\) 两个集合再进一步进行划分,递归处理。

直到 \(ql = qr\),说明当前 \(Q\) 集合内的询问只有 \(ql(qr)\) 一种取值,记录下来就好了。

实现

把初始化也当作一种操作,分治的时候去树状数组里面更新,这样就解决了 \(t\) 的问题,注意分治下一层的时候要清空树状数组。

时间复杂度:\(O(n\log n\log C)\)\(C\) 是值域大小,离散化之后可以达到 \(O(n\log ^2n)\)​,不过差不多。

哎呀,好像是什么什么树的模板诶。

// Problem: P3834 【模板】可持久化线段树 2
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3834
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// Author: Moyou
// Copyright (c) 2023 Moyou All rights reserved.
// Date: 2023-02-12 13:06:13

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

const int N = 1e6 + 10;
const int INF = 1e9 + 7;

struct OPT
{
    int l, r, k, id, type; // type = 2时代表时询问,否则是初始化操作
} q[N], q1[N], q2[N];
int cnt;

int n, m;
int ans[N];

int tr[N << 1];

int lowbit(int x) {return x & (-x); }

void add(int u, int v)
{
    while(u <= n)
    {
        tr[u] += v;
        u += lowbit(u);
    }
}

int query(int u)
{
    int res = 0;
    while(u)
    {
        res += tr[u];
        u -= lowbit(u);
    }
    return res;
}

void solve(int ql, int qr, int l, int r)
{
    int mid = l + r >> 1;
    if(ql > qr) return ;
    if(l == r)
    {
        for(int i = ql; i <= qr; i ++)    
            if(q[i].type == 2)
                ans[q[i].id] = l;
        return ;
    }
    int cnt1 = 0, cnt2 = 0;
    for(int i = ql; i <= qr; i ++)
    {
        if(q[i].type == 1)
        {
            if(q[i].l <= mid)
            {
                add(q[i].id, 1);
                q1[++ cnt1] = q[i];
            }
            else q2[++ cnt2] = q[i];
        }
        else
        {
            int t = query(q[i].r) - query(q[i].l - 1);
            if(t >= q[i].k) q1[++ cnt1] = q[i];
            else q[i].k -= t, q2[++ cnt2] = q[i]; // 注意对于集合Q2,排名变成了k-t
        }
    }
    for(int i = 1; i <= cnt1; i ++) // 消除影响
        if(q1[i].type == 1)
            add(q1[i].id, -1);
    for(int i = 1; i <= cnt1; i ++)
        q[i + ql - 1] = q1[i];
    for(int i = 1; i <= cnt2; i ++)
        q[i + ql + cnt1 - 1] = q2[i];
    solve(ql, ql + cnt1 - 1, l, mid);
    solve(ql + cnt1, qr, mid + 1, r);
    
}

signed main()
{
    cin >> n >> m;
    for(int i = 1, tmp; i <= n; i ++)
        cin >> tmp, q[++ cnt] = {tmp, 1, INF, i, 1};
    for(int i = 1, l, r, k; i <= m; i ++)
        cin >> l >> r >> k, q[++ cnt] = {l, r, k, i, 2};
    solve(1, cnt, -INF, INF);

    for(int i = 1; i <= m; i ++) cout << ans[i] << endl;
    return 0;
}

带修整体二分

P2617 Dynamic Rankings

给定一个含有 \(n\) 个数的序列 \(a_1,a_2 \dots a_n\),需要支持两种操作:

  • Q l r k 表示查询下标在区间 \([l,r]\) 中的第 \(k\) 小的数

  • C x y 表示将 \(a_x\) 改为 \(y\)

\(1\le n,m \le 10^5\)\(1 \le l \le r \le n\)\(1 \le k \le r-l+1\)\(1\le x \le n\)\(0 \le a_i,y \le 10^9\)

这题有解法花里胡哨,其中一个是线段树套平衡树,在线段树的树节点上挂着平衡树,并把原区间拆分成线段树区间,然后二分值域,时间复杂度:\(O(n\log^3n)\)

整体二分应该是最简单的解法,并且可以做到 \(O(n\log ^2n)\)

和上一个标题的思路一样,把初始化、修改、查询一起当作一个 “操作” 放进 \(Q\) 中进行分治,注意修改可以解剖为:减少原来的值然后增加新的值,然后就没有难点了。

// Author: Moyou
// Copyright (c) 2023 Moyou All rights reserved.

struct OPT
{
    int l, r, k, type, id;
} q[N], q1[N], q2[N];
int cnt;
int ans[N];
int a[N];

int tr[N];
int n, m;

void add(int u, int v)
int query(int u)

void solve(int ql, int qr, int l, int r)
{
    if (ql > qr || l > r)
        return;
    int mid = l + r >> 1;
    if (l == r)
    {
        for (int i = ql; i <= qr; i++)
            if (q[i].type == 1)
                ans[q[i].id] = l;
        return;
    }
    int cnt1 = 0, cnt2 = 0;
    for (int i = ql; i <= qr; i++)
    {
        if (q[i].type == 0)
        {
            if (q[i].k <= mid)
                add(q[i].l, q[i].r), q1[++cnt1] = q[i];
            else
                q2[++cnt2] = q[i];
        } 
        else
        {
            int t = query(q[i].r) - query(q[i].l - 1);
            if (t >= q[i].k)
                q1[++cnt1] = q[i];
            else
                q[i].k -= t, q2[++cnt2] = q[i];
        }
    }

    for (int i = 1; i <= cnt1; i++)
        if (q1[i].type == 0)
            add(q1[i].l, -q1[i].r);
    for (int i = 1; i <= cnt1; i++)
        q[i + ql - 1] = q1[i];
    for (int i = 1; i <= cnt2; i++)
        q[i + ql + cnt1 - 1] = q2[i];
    solve(ql, ql + cnt1 - 1, l, mid);
    solve(ql + cnt1, qr, mid + 1, r);
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        q[++cnt] = {i, 1, a[i], 0};
    }
    for (int i = 1; i <= m; i++)
    {
        char op;
        cin >> op;
        if (op == 'C')
        {
            int x, y;
            cin >> x >> y;
            q[++cnt] = {x, -1, a[x], 0, 0};
            a[x] = y;
            q[++cnt] = {x, 1, y, 0, 0};
        }
        else
        {
            int a, b, c;
            cin >> a >> b >> c;
            q[++cnt] = {a, b, c, 1, i};
        }
    }
    for (int i = 1; i <= m; i++)
        ans[i] = -1;
    solve(1, cnt, 0, INF);
    for (int i = 1; i <= m; i++)
        if (ans[i] != -1)
            cout << ans[i] << endl;

    return 0;
}
posted @ 2023-02-12 18:00  MoyouSayuki  阅读(52)  评论(0编辑  收藏  举报
:name :name