• 《算法竞赛进阶指南》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]);
        }
    }
    每一个不曾起舞的日子,都是对生命的辜负。
  • 相关阅读:
    Linux三剑客awk命令试题
    Linux综合练习题
    Linux系统用户角色划分
    Linux添加磁盘fdisk命令
    Linux的七种运行级别
    Linux 文件类型
    Linux开机启动程序
    Linux软件安装
    linux运行级别
    Linux /etc目录下的重要配置文件
  • 原文地址:https://www.cnblogs.com/randy-lo/p/13360471.html
Copyright © 2020-2023  润新知