《算法竞赛进阶指南》0x47离线分治算法 基于值域的整体分治求解区间第K小

题目链接:https://www.acwing.com/problem/content/description/257/

基于值域的整体分治策略是给定mid,将值小于mid的数的下标放入lq,另外的放入rq,将询问也进行分割,变成两个独立的子问题,对子问题进行求解即可。注意树状数组每次结束一次solve后进入下一次solve函数之前必须是空的,保证空间充分利用。还有,可以充分利用原来分空间,将更改后的lq,rq再复制回[st,ed]区间中进行下一轮计算,在solve中指定可用区间,因为分治的两个子问题是不相交地,时间复杂度约为O(nlog^2n)。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100010;
const int inf = 1e9;
struct node{
    int op,x,y,z;
}q[maxn<<1],lq[maxn<<1],rq[maxn<<1];
int ans[maxn];
int c[maxn];
int n,m;
void update(int x,int y){
    while(x<=n){//存的是下标 
        c[x]+=y; 
        x+=x&-x;
    }
}
int query(int x){
    int ans=0;
    while(x){
        ans+=c[x];
        x-=x&-x;
    }
    return ans;
}
void solve(int lval,int rval,int st,int ed){
    if(st>ed)return ;
    if(lval==rval){
        for(int i=st;i<=ed;i++){
            if(q[i].op>0)
                ans[q[i].op]=lval;
        }
        return;
    } 
    int lt=0,rt=0;
    int mid=(lval+rval)>>1;
    for(int i=st;i<=ed;i++){
        if(q[i].op==0){//所有的插入操作都在查询操作之后进行,对插入进行分割 
            if(q[i].y<=mid)update(q[i].x,1),lq[++lt]=q[i];
            else rq[++rt]=q[i];
        }else{//对查询进行分割 
            int tmp=query(q[i].y)-query(q[i].x-1);
            if(tmp>=q[i].z)lq[++lt]=q[i];//在[lval,mid]中能找到第k小
            else q[i].z-=tmp,rq[++rt]=q[i];//在[mid+1,rval]中找第k-tmp小的数 
        }
    }
    for(int i=st;i<=ed;i++){//树状数组复原 
        if(q[i].op==0 && q[i].y<=mid)update(q[i].x,-1);
    }
    for(int i=1;i<=lt;i++)q[st+i-1]=lq[i];//复制回原来的空间继续使用
    for(int i=1;i<=rt;i++)q[st+lt+i-1]=rq[i];
    
    solve(lval,mid,st,st+lt-1);
    solve(mid+1,rval,st+lt,ed);
}
int main(){
    cin>>n>>m;
    int t=0;
    for(int i=1;i<=n;i++){//询问之前记录查询 
        int val;scanf("%d",&val);
        q[++t].op=0;q[t].x=i,q[t].y=val;
    }
    for(int i=1;i<=m;i++){//记录询问 
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        q[++t].op=i;
        q[t].x=l,q[t].y=r,q[t].z=k;
    }
    solve(-inf,inf,1,t);//值域上的整体分治
    for (int i=1;i<=m;i++)printf("%d
",ans[i]);
}

 带动态修改的第K小查询就是改了一下对插入的划分方式,使得在一段中查询和修改能同时进行,顺序保持。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010, INF = 1e9;
struct rec {int op, x, y, z;} q[3 * N], lq[3 * N], rq[3 * N];
int T, n, m, t, p, a[N], c[N], ans[N];

int ask(int x) {
    int y = 0;
    for (; x; x -= x & -x) y += c[x];
    return y;
}

void change(int x, int y) {
    for (; x <= n; x += x & -x) c[x] += y;
}

void solve(int lval, int rval, int st, int ed) {
    if (st > ed) return;
    if (lval == rval) {
        for (int i = st; i <= ed; i++)
            if (q[i].op > 0) ans[q[i].op] = lval;
        return;
    }
    int mid = (lval + rval) >> 1;
    int lt = 0, rt = 0;
    for (int i = st; i <= ed; i++) {
        if (q[i].op <= 0) { // 是一次修改
            if (q[i].y <= mid) change(q[i].x, q[i].z), lq[++lt] = q[i];
            else rq[++rt] = q[i];
        } else { // 是一次询问
            int cnt = ask(q[i].y) - ask(q[i].x - 1);
            if (cnt >= q[i].z) lq[++lt] = q[i];
            else q[i].z -= cnt, rq[++rt] = q[i];
        }
    }
    for (int i = ed; i >= st; i--) { // 还原树状数组
        if (q[i].op <= 0 && q[i].y <= mid) change(q[i].x, -q[i].z);
    }
    for (int i = 1; i <= lt; i++) q[st + i - 1] = lq[i];
    for (int i = 1; i <= rt; i++) q[st + lt + i - 1] = rq[i];
    solve(lval, mid, st, st + lt - 1);
    solve(mid + 1, rval, st + lt, ed);
}

int main() {
    cin >> T;
    while (T--) {
        cin >> n >> m;
        t = p = 0;
        for (int i = 1; i <= n; i++) {
            int val; scanf("%d", &val);
            // 等价于在第i个位置上加入一个数val
            q[++t].op = 0, q[t].x = i, q[t].y = val, q[i].z = 1;
            a[i] = val;
        }
        for (int i = 1; i <= m; i++) {
            char op[2]; scanf("%s", op);
            if (op[0] == 'Q') {
                int l, r, k; scanf("%d%d%d", &l, &r, &k);
                // 记录一次询问
                q[++t].op = ++p, q[t].x = l, q[t].y = r, q[t].z = k;
            } else {
                int x, y; scanf("%d%d", &x, &y);
                // 去掉原来的数a[x]
                q[++t].op = -1, q[t].x = x, q[t].y = a[x], q[t].z = -1;
                // 在第x个位置上加入一个新的数y
                q[++t].op = 0, q[t].x = x, q[t].y = y, q[t].z = 1;
                a[x] = y;
            }
        }
        // 基于值域对t=n+m个操作进行整体分治
        solve(0, INF, 1, t);
        for (int i = 1; i <= p; i++)
            printf("%d
", ans[i]);
    }
}
每一个不曾起舞的日子,都是对生命的辜负。
原文地址:https://www.cnblogs.com/randy-lo/p/13360471.html