【学习笔记】整体二分

一. 整体二分概念

整体二分的主体思路就是把多个查询一起解决,是一个离线算法。

其要求:

  1. 询问的答案具有可二分性

  2. 修改对判定答案的贡献互相独立,修改之间互不影响效果

  3. 修改如果对判定答案有贡献,则贡献为一确定的与判定标准无关的值

  4. 贡献满足交换律,结合律,具有可加性

  5. 题目允许使用离线算法

其大体结构框架:

\([l,r]\) 为答案的值域\([s,t]\) 为答案的定义域

那么二分答案时考虑下标在 \([s,t]\) 中的操作和询问,答案在值域 \([l,r]\)

二. 静态区间第 \(k\)

由一道题引入整体二分的具体实现。

给定 \(n\) 个整数构成的序列 \(a\),将对于指定的闭区间 \([l, r]\) 查询其区间内的第 \(k\) 小值。

\(1 \leq n,m \leq 2\times 10^5\)\(|a_i| \leq 10^9\)\(1 \leq l \leq r \leq n\)\(1 \leq k \leq r - l + 1\)

静态区间第 \(k\) 小,一个经典的问题。

显然可以用主席树做,这里我们考虑用整体二分。

cdq 分治类似,将询问和修改都看成“操作”。我们首先把所有操作按时间顺序存入数组中,然后开始分治,定义函数

solve(l,r,s,t) 表示在值域 \([l,r]\) 上二分处理 \([s,t]\) 这些操作

在每一层分治中,考虑利用数据结构(常用树状数组)统计当前查询的答案和 \(mid =\dfrac{l+r}{2}\) 之间的关系。

根据查询出来的答案和 \(mid\) 间的关系(\(<=mid\) 或者 \(>mid\) )将当前处理的操作序列分为两份,并分别递归处理(注意修改和询问都要递归)。

边界:当 \(l=r\) 时,找到答案,记录答案并返回即可。

需要注意的是,在整体二分过程中,solve(l,r,s,t) 只处理答案在 \([l,r]\) 内的询问,最终答案范围不在 \([l, r]\) 的询问会在其他 solve 函数中处理。

比如,我们现在有这样一个 \(n=7\) 的序列:

\([1,5,3,6,4,7,2]\)

查询次数为 \(3:\)

\(q_1=2,5,3\to ans=4\)

\(q_2=4,4,1\to ans=6\)

\(q_3=1,6,2\to ans=3\)

考虑用整体二分的思想。

原序列的值域显然为 \([1,7]\)

这里注意最好先将原序列离散化后求值域。

那么 \(mid=\dfrac{1+7}{2}=4\)

考虑将这个序列分成两类:

一类是权值 \(\le 4\),另一类是 \(>4\) 的。

我们把这两类用下标表示出来:

\(\le 4:(1,3,5,7)\)

\(>4:(2,4,6)\)

注意这里指下标,而不是权值。

考虑第一个询问 \(q_1=2,5,3\)

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

const int N=1e6+6;
const int inf=1e9;

int n,m,tr[N],ans[N];

struct node{
	int x,y,k,id;
    bool flag;
}a[N],b[N],c[N];

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

inline void update(int x,int val){
    while(x<=n){
        tr[x]+=val;
        x+=lowbit(x);
    }
    return;
}

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

inline void solve(int l,int r,int s,int t){
    if(s>t)return;
    if(l==r){
        for(int i=s;i<=t;i++)if(a[i].flag)ans[a[i].id]=l;
		return;
    }
    int mid=(l+r)>>1,p=0,q=0;
    for(int i=s;i<=t;i++){
        if(!a[i].flag){
            if(a[i].x<=mid){
                update(a[i].id,1);
                b[++p]=a[i];
            }
            else c[++q]=a[i];
        }
        else{
            int res=query(a[i].y)-query(a[i].x-1);
            if(res>=a[i].k)b[++p]=a[i];
            else{
                a[i].k-=res;
                c[++q]=a[i];
            }
        }
    }
    for(int i=1;i<=p;i++)if(!b[i].flag)update(b[i].id,-1);
	for(int i=1;i<=p;i++)a[i+s-1]=b[i];
	for(int i=1;i<=q;i++)a[i+s+p-1]=c[i];
	solve(l,mid,s,s+p-1);
	solve(mid+1,r,s+p,t);
    return;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        a[i].x=x;
        a[i].y=1;
        a[i].k=inf;
        a[i].id=i;
        a[i].flag=false;
    }
    for(int i=1;i<=m;i++){
        int x,y,k;
        scanf("%d%d%d",&x,&y,&k);
        a[i+n].x=x;
        a[i+n].y=y;
        a[i+n].k=k;
        a[i+n].id=i;
        a[i+n].flag=true;
    }
    solve(-inf,inf,1,n+m);
    for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
    return 0;
}
posted @ 2022-01-07 19:22  trsins  阅读(108)  评论(0编辑  收藏  举报