题目链接: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]); } }