洛谷P2617 Dynamic Rankings 树状数组+主席树

题目链接:传送门
题目大意:
给出一个数列a,要求资瓷以下两种操作:
1.把 a [ i ] a[i] a[i]的值修改为 t t t
2.询问 [ l , r ] [l,r] [l,r]区间内的第 k k k小值。
时限 2 s 2s 2s,数的个数和操作次数不超过 1 e 5 1e5 1e5

看到 k k k小值就想到用主席树qwq。
然后发现要资瓷修改操作。
不修改的主席树的第 i i i棵维护的是 1 ~ i 1~i 1i的前缀。
所以如果要暴力修改,就要把 [ i , n ] [i,n] [i,n]的主席树都修改一遍,然后复杂度就炸了QAQ。
于是再思考问题的本质:
修改:单点修改。
询问:区间查询时,把 [ 1 , r ] [1,r] [1,r]的权值线段树减去 [ 1 , l − 1 ] [1,l-1] [1,l1]的权值线段树 − > -> >区间查询。
单点修改区间查询?
考虑套一个树状数组:

类似于树状数组:
第1棵主席树表示第1个点的权值的集合。
第2棵主席树表示第1、2个点的权值的集合。
第3棵主席树表示第3个点的权值的集合。
第4棵主席树表示第1、2、3、4个点的权值的集合。

这样每次最多修改 l o g n logn logn棵主席树,询问也是 l o g n logn logn棵主席树一起加/减。

几个要注意的点:
1.主席树修改的时候是在自己的版本基础上修改
2.先把所有的询问读进去再离散化,不然新的数在离散化数组中找不到。
总时间复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

代码:

#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
inline char GetChar() {
	char ch=getchar();
	while(ch!='Q' && ch!='C')	ch=getchar();
	return ch;
}
const int Size=100005;
const int LOG=400;
const double eps=1e-4;
namespace I_Love {

int n,m,tot,maxn,a[Size],b[Size<<1],T[Size];
int ls[Size*LOG],rs[Size*LOG],sum[Size*LOG];
int op[Size],x[Size],y[Size],z[Size];
int update(int pre,int l,int r,int x,int val) {
	//主席树的修改操作
	int rt=++tot;
	ls[rt]=ls[pre]; rs[rt]=rs[pre];
	sum[rt]=sum[pre]+val;
	if(l<r) {
		int mid=(l+r)>>1;
		if(x<=mid) {
			ls[rt]=update(ls[pre],l,mid,x,val);
		} else {
			rs[rt]=update(rs[pre],mid+1,r,x,val);
		}
	}
	return rt;
}
int lpos,rpos,tmpl[Size],tmpr[Size];
int query(int l,int r,int k) {
	//主席树的查询操作
	if(l==r)	return l;
	int mid=(l+r)>>1,ans=0;
	//logn棵主席树一起加/减
	for(re i=1; i<=lpos; i++)	ans-=sum[ls[tmpl[i]]];
	for(re i=1; i<=rpos; i++)	ans+=sum[ls[tmpr[i]]];
	if(k<=ans) {
		for(re i=1; i<=lpos; i++)	tmpl[i]=ls[tmpl[i]];
		for(re i=1; i<=rpos; i++)	tmpr[i]=ls[tmpr[i]];
		return query(l,mid,k);
	}
	for(re i=1; i<=lpos; i++)	tmpl[i]=rs[tmpl[i]];
	for(re i=1; i<=rpos; i++)	tmpr[i]=rs[tmpr[i]];
	return query(mid+1,r,k-ans);
}
inline int lowbit(int x) {
	return x&(-x);
}
void Update(int x,int t) {
	//查询a[x]在离散化数组b中的位置
	int val=lower_bound(b+1,b+1+maxn,a[x])-b;
	for(re i=x; i<=n; i+=lowbit(i)) {
		T[i]=update(T[i],1,maxn,val,t);
	}
}
int Query(int l,int r,int k) {
	lpos=rpos=0;
	for(re i=l-1; i; i-=lowbit(i)) {
		tmpl[++lpos]=T[i];
	}
	for(re i=r; i; i-=lowbit(i)) {
		tmpr[++rpos]=T[i];
	}
	return query(1,maxn,k);
}
void Fujibayashi_Ryou() {
	maxn=n=read();
	m=read();
	for(re i=1; i<=n; i++) {
		b[i]=a[i]=read();
	}
	for(re i=1; i<=m; i++) {
		char ch=GetChar();
		x[i]=read();
		y[i]=read();
		if(ch=='C') {
			op[i]=2;
			b[++maxn]=y[i];
		} else {
			op[i]=1;
			z[i]=read();
		}
	}
	sort(b+1,b+1+maxn);
	maxn=unique(b+1,b+1+maxn)-(b+1);
	for(re i=1; i<=n; i++) {
		Update(i,1);
	}
	for(re i=1; i<=m; i++) {
		if(op[i]==2) {
			Update(x[i],-1);
			a[x[i]]=y[i];
			Update(x[i],1);
		} else {
			printf("%d\n",b[Query(x[i],y[i],z[i])]);
		}
	}
}

}
int main() {
	I_Love::Fujibayashi_Ryou();
	return 0;
}

Ps.题目说“写法正常的整体二分和树套树都可以以大约1000ms每个点的时间过”。
然而我开了氧气优化还是跑不进 1 s 1s 1s。。
感受到了题目对大常数选手的恶意QWQ

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值