树状数组套权值线段树

Dynamic Rankings

https://www.luogu.com.cn/problem/P2617

题目描述

给定一个含有 n n n 个数的序列 a 1 , a 2 … a n a_1,a_2 \dots a_n a1,a2an,需要支持两种操作:

  • Q l r k 表示查询下标在区间 [ l , r ] [l,r] [l,r] 中的第 k k k 小的数
  • C x y 表示将 a x a_x ax 改为 y y y

输入格式

第一行两个正整数 n , m n,m n,m,表示序列长度与操作个数。
第二行 n n n 个整数,表示 a 1 , a 2 … a n a_1,a_2 \dots a_n a1,a2an
接下来 m m m 行,每行表示一个操作,都为上述两种中的一个。

输出格式

对于每一次询问,输出一行一个整数表示答案。

样例 #1

样例输入 #1

5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3

样例输出 #1

3
6

提示

【数据范围】

对于 10 % 10\% 10% 的数据, 1 ≤ n , m ≤ 100 1\le n,m \le 100 1n,m100
对于 20 % 20\% 20% 的数据, 1 ≤ n , m ≤ 1000 1\le n,m \le 1000 1n,m1000
对于 50 % 50\% 50% 的数据, 1 ≤ n , m ≤ 1 0 4 1\le n,m \le 10^4 1n,m104
对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 1 0 5 1\le n,m \le 10^5 1n,m105 1 ≤ l ≤ r ≤ n 1 \le l \le r \le n 1lrn 1 ≤ k ≤ r − l + 1 1 \le k \le r-l+1 1krl+1 1 ≤ x ≤ n 1\le x \le n 1xn 0 ≤ a i , y ≤ 1 0 9 0 \le a_i,y \le 10^9 0ai,y109

请注意常数优化,但写法正常的整体二分和树套树都可以以大约 1000 ms 1000\text{ms} 1000ms 每个点的时间通过。

来源:bzoj1901

本题数据为洛谷自造数据,使用CYaRon耗时5分钟完成数据制作。

学习笔记:

动态维护区间 k k k / / /大值。
我们做过静态的区间 k k k / / /大值,是采用前缀和的思想建前缀树,即主席树来解的。那么动态时,当更新节点时,我们需要改每颗树上的 l o g log log个节点,单次复杂度达到 n l o g n nlogn nlogn,显然不可取。
那么我们同样看看,用前缀和的思想,能不能像权值线段树那样,模仿主席树那样跳节点找 k k k小呢 ? ?
此时我们的外层就不在是主席树那样简单的 i − > i + 1 i->i+1 i>i+1的前缀方式了,这样会导致 n n n棵树互相关联;
我们不想让他们关联,要方便修改,那么前缀就要换一种方式。
此时就可以套上树状数组了。我们在树状数组的每个节点,代表的那一段区间里,开一个权值线段树,然后树状数组每个节点记录下此节点权值线段树里,数的个数,到时候我们查询时,就像主席树那样,用 1 1 1 r r r的总区间减去 1 1 1 l − 1 l-1 l1总区间,即得到 l l l r r r的权值情况。然后,树状数组里 1 1 1 r r r的总区间可能包含多个节点才能凑出来,所以不像主席树是两棵树在跳,这里是 2 ∗ l o g n 2*logn 2logn棵树一起跳,因为他们身上都有必要的前缀和信息。
所以树状数组的作用是保存的是该区间值域内数的个数,方便查询;每个树状数组节点下的权值线段树的作用是维护该区间值域内数的个数,方便修改。
复杂度就不说了,大佬那里说的很清楚。

给个板子:

/*keep on going and never give up*/
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define db(x) cerr<<(#x)<<" "<<(x)<<" "<<endl;
#define endl "\n"
#define INF 1e17
#define fast std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
const double E = exp(1);
const double PI = acos(-1.0);
const int maxn=2e5+10; 
struct sgt{
	int v,ls,rs;
}t[maxn*100];
struct dd{
	bool b;
	int l,r,k;
	int pos,t;
}q[maxn];
int n,m,a[maxn],id[maxn<<1],rt[maxn],len,tot,temp[2][20],cnt[2];
char op;
void update(int &now,int l,int r,int pos,int val){
    if (!now) now=++tot;
    t[now].v+=val;
    if (l==r) return;
    int mid=l+r>>1;
    if (pos<=mid) update(t[now].ls,l,mid,pos,val);
    else update(t[now].rs,mid+1,r,pos,val);
}
void _update(int x,int val){
    int k=lower_bound(id+1,id+len+1,a[x])-id;
    for (int i=x;i<=n;i+=i&-i) update(rt[i],1,len,k,val);
}
int ask(int l,int r,int k){
    if (l==r) return l;
    int mid=l+r>>1,sum=0;
    for (int i=1;i<=cnt[1];i++) sum+=t[t[temp[1][i]].ls].v;
    for (int i=1;i<=cnt[0];i++) sum-=t[t[temp[0][i]].ls].v;
    if (k<=sum){
        for (int i=1;i<=cnt[1];i++) temp[1][i]=t[temp[1][i]].ls;
        for (int i=1;i<=cnt[0];i++) temp[0][i]=t[temp[0][i]].ls;
        return ask(l,mid,k);
    }
    else{
        for (int i=1;i<=cnt[1];i++) temp[1][i]=t[temp[1][i]].rs;
        for (int i=1;i<=cnt[0];i++) temp[0][i]=t[temp[0][i]].rs;
        return ask(mid+1,r,k-sum);
    }
}
int _ask(int l,int r,int k){
    memset(temp,0,sizeof(temp));
    cnt[0]=cnt[1]=0;
    for (int i=r;i;i-=i&-i) temp[1][++cnt[1]]=rt[i];
    for (int i=l-1;i;i-=i&-i) temp[0][++cnt[0]]=rt[i];
    return ask(1,len,k);
}
signed main(){
    fast
    cin>>n>>m;
    for (int i=1;i<=n;i++) cin>>a[i],id[++len]=a[i];
    for (int i=1;i<=m;i++){
        cin>>op;
        q[i].b=(op=='Q');
        if (q[i].b)    cin>>q[i].l>>q[i].r>>q[i].k;
        else cin>>q[i].pos>>q[i].t,id[++len]=q[i].t;
    }
    sort(id+1,id+len+1);
    len=unique(id+1,id+len+1)-id-1;
    for (int i=1;i<=n;i++) _update(i,1);
    for (int i=1;i<=m;i++){
        if (q[i].b) printf("%d\n",id[_ask(q[i].l,q[i].r,q[i].k)]);
        else{
            _update(q[i].pos,-1);
            a[q[i].pos]=q[i].t;
            _update(q[i].pos,1);
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值