\(\href{https://www.luogu.com.cn/problem/P2617}{P2617 Dynamic Rankings}\)
题目描述
给定一个含有 \(n\) 个数的序列 \(a_1,a_2 \dots a_n\),需要支持两种操作:
Q l r k
表示查询下标在区间 \([l,r]\) 中的第 \(k\) 小的数C x y
表示将 \(a_x\) 改为 \(y\)
洛谷题解写的依托答辩,什么主席树,什么维护位置,都不是。
带修改的主席树本身就是不存在的,因为主席树的两个根节点如果有共用节点,修改共用节点时会导致另一个的根节点维护的东西出问题,所以纯的主席树不可能实现修改操作,主席树就相当于建好了,不能动,只能用的那种东西,只不过时间和空间都很优秀罢了。
对于本题,本质其实是树套树,首先考虑暴力硬做,维护一个数组,修改 \(O(1)\) ,查询 \(O(n)\) ,总共 \(O(nm)\) 直接寄掉。先考虑这道题如果没有修改操作,那么就是裸的主席树板子,不会的见 \(\href{https://www.luogu.com.cn/problem/P3834}{P3834}\) ,然后考虑修改操作,上文说过了,主席树不支持修改操作,那我们就把主席树拆开,主席树的本质就是一堆权值线段树的前缀和,现在维护前缀和与值无法同时维护,就把权值线段树单拎出来,独立出来,不共用节点,但为了维护空间复杂度,所以使用动态开点权值线段树,对于前缀和,利用树状数组进行维护,可以把树状数组上的节点视作其所管辖的动态开点权值线段树的合并,想要查询对应区间,就只需要 \(lowbit\) 一下,就行了。
这样做的话,每次修改操作只需要修改树状数组上对应的那条链上的 \(log(n)\) 个动态开点权值线段树,而每个动态开点权值线段树也只需要维护/增添链上的 \(log(n)\) 个节点,而查询操作,则至多查询树状数组上的 \(log(n)\) 个动态开点权值线段树,每次查询的时候让 \(log(n)\) 个线段树一起向左右子节点跳,因为它们所管辖的区间一模一样,所以这样实现的时空复杂度均为 \(O(nlog^2(n))\) ,也不是洛谷题解上说的时间 \(O(nlog^2(n))\) 空间 \(O(nlog(n))\) 。
如果你无法理解本题为什么不是主席树,那你就想想如果我们只需要一个数据结构,让它满足单点修改和区间查询,先不考虑它如何进行单点修改和区间查询,那么就是树状数组或者线段树(本题常数大,会寄),确定好后,我们再想怎么优秀的进行单次的单点修改和区间查询,发现权值线段树是最好的选择,但空间 \(O(n^2log(n))\) 会寄,所以用动态开点优化到 \(O(nlog^2(n))\) 。
所以本题说是可持久化线段树(主席树),倒不如说是借助了主席树前缀和思想(虽然树状数组也有)的树套树题目,和主席树没关系,能想到它,纯粹是为了进一步优化,结果把自己优化没了。
最后提一句,你谷的题解看看就行了,简单的会的没必要看的,难的题解胡扯或只贴个代码,如果你想认真学,看了题解,写完代码,自己分析一遍每一行是干啥的,别写完了啥也不会。
注意:因为我们需要在 \(log(n)\) 个权值线段树上一起跳,每一次记录需要跳的节点是一定不要用 \(memset\) ,否则 \(n\) 次的 \(1e6\) 的清零会直接T飞了(我不会告诉你我因为这调了两个小时)
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define intt __int128
#define if_debug 0int read()
{int x = 0,f = 1;char ch = getchar();while(ch<'0'||'9'<ch){if(ch=='-'){f = -1;}ch = getchar();}while('0'<=ch&&ch<='9'){x = (x<<3) + (x<<1) + ch - '0';ch = getchar();}return x * f;
}const int N = 1e6 + 10,inf = 1e18;int n,m,a[N],temp[N],cnt,root[N],it,cnt1,cnt2,tot;
int need1[N],need2[N];struct optt
{bool op;int l,r,x,y,k;
}opt[N];struct tree
{struct node{int l,r,sum;}t[20000000];void update(int &i,int l,int r,int it,int x){if(!i){i = ++tot;}t[i].sum += x;if(l==r){return ;}int mid = (l+r)>>1;if(it<=mid){update(t[i].l,l,mid,it,x);}else{update(t[i].r,mid+1,r,it,x);}}int query(int l,int r,int k){if(l==r){return l;}int mid = (l+r)>>1,sum = 0;for(int i = 1;i<=cnt2;i++){sum += t[t[need2[i]].l].sum;}for(int i = 1;i<=cnt1;i++){sum -= t[t[need1[i]].l].sum;}if(k<=sum){for(int i = 1;i<=cnt1;i++){need1[i] = t[need1[i]].l;}for(int i = 1;i<=cnt2;i++){need2[i] = t[need2[i]].l;}return query(l,mid,k);}else{for(int i = 1;i<=cnt1;i++){need1[i] = t[need1[i]].r;}for(int i = 1;i<=cnt2;i++){need2[i] = t[need2[i]].r;}return query(mid+1,r,k-sum);}}
}tr;int lowbit(int it)
{return it & -it;
}int query(int l,int r,int k)
{//memset(need1,0,sizeof need1)//memset(need2,0,sizeof need2)cnt1 = cnt2 = 0;for(int i = l-1;i;i-=lowbit(i)){need1[++cnt1] = root[i];}for(int i = r;i;i-=lowbit(i)){need2[++cnt2] = root[i];}return tr.query(1,cnt,k);
}signed main()
{#if if_debugfreopen("1.in","r",stdin);freopen("2.txt","w",stdout);#endifios::sync_with_stdio(false);cin>>n>>m;for(int i = 1;i<=n;i++){cin>>a[i];temp[++cnt] = a[i];}for(int i = 1;i<=m;i++){char s;cin>>s;if(s=='Q'){opt[i].op = 1;cin>>opt[i].l>>opt[i].r>>opt[i].k;}else{opt[i].op = 0;cin>>opt[i].x>>opt[i].y;temp[++cnt] = opt[i].y;}}sort(temp+1,temp+1+cnt);cnt = unique(temp+1,temp+1+cnt) - temp - 1;for(int i = 1;i<=n;i++){int it = lower_bound(temp+1,temp+cnt+1,a[i]) - temp;for(int j = i;j<=n;j+=lowbit(j)){tr.update(root[j],1,cnt,it,1);}}for(int i = 1;i<=m;i++){if(opt[i].op){cout<<temp[query(opt[i].l,opt[i].r,opt[i].k)]<<endl;}else{int it = lower_bound(temp+1,temp+1+cnt,a[opt[i].x]) - temp;for(int j = opt[i].x;j<=n;j+=lowbit(j)){tr.update(root[j],1,cnt,it,-1);}it = lower_bound(temp+1,temp+1+cnt,opt[i].y) - temp;for(int j = opt[i].x;j<=n;j+=lowbit(j)){tr.update(root[j],1,cnt,it,1);}a[opt[i].x] = opt[i].y;}}
}