3065: 带插入区间K小值

本文详细解析了一个使用替罪羊树套权值线段树解决区间k小值在线查询问题的算法实现。介绍了算法的基本思想、关键步骤及调试过程中遇到的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

快捷版题意:带插入、修改的区间k小值在线查询。

我太菜了啊啊啊啊啊啊啊啊啊。

看题解+膜代码用了一上午,又写+调了一上午,起码改了十几个地方。

思路:替罪羊树套权值线段树。

首先平衡树当然是维护区间,每个节点上的线段树代表其子树的权值状态。

首先是插入操作。因为全程%hzwer所以抄了他的打法。找到平衡树中位置为k-1的节点,跳右儿子,这时传进去的k值一定为0,所以能一直跳k-1的右儿子的左儿子。加入就可以了。

因为是平衡树所以可能要重建一下。

更改略简单,具体可以看看代码。

重点是查询,如果是静态区间第k小是经典的例题,但这题区间转化成了不带转动的平衡树,所以要把对应的子树或节点提取出来,再一起二分。

因为语文太弱,无法表达。大致跟线段树差不多,但如果查询区间包含了当前根节点,要另开一个数组记录下来。然后就是喜闻乐见的二分了。(我也不知道自己在讲什么,还是看代码吧。)

还有要打一个回收站,回收线段树多余空间,当暴力重建替罪羊树时每删一个点就把其对应的线段树放入回收站里,当一个线段树上的点值为0时,就把整棵子树放入回收站。


调试笔记:

1、回收站莫名奇妙的错了。

2、动态开点线段树要打成change(tr[x].lc/rc,……)。

3、build时要将v[dfn[i]]的值加入而不是v[i]。

4、注意insert中if条件不能打错。

5、几乎所有有关替罪羊树,回收站的函数都要加指针。

6、提取区间时1)要判断边界 2)前后统一,如我要记录节点的跟的位置 3)特殊情况判完后该return的一定要return return return!!! 4)传给右儿子的区间从1开始。

7、二分时只要提取出的右子树,和部分节点,条件不能错。

8、……(不好意思写了)

都是很SB的错误,自己弱怪谁。

code:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
queue <int> rec;
using namespace std;
const double eq=0.75;
struct trnode{
	int lc,rc,c;
	trnode(){lc=rc=c=0;}
}tr[10000005];int root[70005],trlen=0,rt=0;
int n,m,son[70005][2],v[70005],tot=0,tmp=0,lastans=0;
int dfn[70005],top;
inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x;
}
void reclaim(int &x)
{
	if(x==0) return;
	rec.push(x);
	reclaim(tr[x].lc);reclaim(tr[x].rc);
	tr[x].c=tr[x].lc=tr[x].rc=0;x=0;
}
int getnum()
{
	if(rec.empty()){trlen++;return trlen;}
	int ans=rec.front();rec.pop();return ans;
}
void change(int &x,int l,int r,int k,int c)
{
	if(!x) x=getnum();
	if(l==r){tr[x].c+=c;return;}
	int mid=(l+r)/2,lc=tr[x].lc,rc=tr[x].rc;
	if(k<=mid) change(tr[x].lc,l,mid,k,c);
	else change(tr[x].rc,mid+1,r,k,c);
	tr[x].c+=c;
	if(!tr[x].c) reclaim(x);
}
void build(int &x,int l,int r)
{
	if(l>r){x=0;return;}
	int mid=(l+r)/2;x=dfn[mid];
	build(son[x][0],l,mid-1);
	build(son[x][1],mid+1,r);
	for(int i=l;i<=r;i++) change(root[x],0,70000,v[dfn[i]],1);
}
void del(int &x)
{
	if(!x) return; reclaim(root[x]);
	del(son[x][0]);dfn[++top]=x;del(son[x][1]);
	x=0;
}
void rebuild(int &x)
{
	top=0;del(x);
	build(x,1,top);
}
void insert(int &x,int k,int val)
{
	if(!x)
	{
		x=++tot;son[x][0]=son[x][1]=0;v[x]=val;
		change(root[x],0,70000,val,1);
		return;
	}
	change(root[x],0,70000,val,1);
	int l=tr[root[son[x][0]]].c;
	if(k<=l) insert(son[x][0],k,val);
	else insert(son[x][1],k-l-1,val);
	if(tr[root[x]].c*eq>max(tr[root[son[x][0]]].c,tr[root[son[x][1]]].c))
	{
		if(tmp)
		{
			if(son[x][0]==tmp) rebuild(son[x][0]);
			else rebuild(son[x][1]);
			tmp=0;
		}
	}
	else tmp=x;
}
int update(int x,int k,int c)
{
	change(root[x],0,70000,c,1);
	int l=tr[root[son[x][0]]].c,t;
	if(l+1==k)
	{
		change(root[x],0,70000,v[x],-1);
		t=v[x];v[x]=c;return t;
	}
	if(k<=l) t=update(son[x][0],k,c);
	else t=update(son[x][1],k-l-1,c);
	change(root[x],0,70000,t,-1);
	return t;
}
int t[1700],s1,q[1700],s2;
void pre_query(int x,int l,int r)
{
	if(l>r) return;
	int L=tr[root[son[x][0]]].c;
	if(l==1&&r==tr[root[x]].c){t[++s1]=root[x];return;}
	if(r<=L) pre_query(son[x][0],l,r);
	else if(L+1<l) pre_query(son[x][1],l-L-1,r-L-1);
	else
	{
		q[++s2]=v[x];
		pre_query(son[x][0],l,L);
		pre_query(son[x][1],1,r-L-1);
	}
}
int solve_query(int L,int R,int k)
{
	s1=s2=0;pre_query(rt,L,R);
	int l=0,r=70000;
	while(l<r)
	{
		int mid=(l+r)/2,sum=0;
		for(int i=1;i<=s1;i++) sum+=tr[tr[t[i]].lc].c;
		for(int i=1;i<=s2;i++)
			if(l<=q[i]&&q[i]<=mid) sum++;
		if(k<=sum)
		{
			for(int i=1;i<=s1;i++) t[i]=tr[t[i]].lc;
			r=mid;
		}
		else
		{
			for(int i=1;i<=s1;i++) t[i]=tr[t[i]].rc;
			l=mid+1;k-=sum;
		}
	}
	return l;
}
int main()
{
	n=read();
	for(int i=1;i<=n;i++)v[i]=read();
	for(int i=1;i<=n;i++)dfn[i]=i;
	build(rt,1,n);tot=n;
	m=read();
	char ch[2];int x,y,K;
	while(m--)
	{
		scanf("%s",ch);
		x=read();y=read();x^=lastans;y^=lastans;
		switch(ch[0])
		{
		case 'Q':K=read();K^=lastans;lastans=solve_query(x,y,K);printf("%d\n",lastans);break;
		case 'M':update(rt,x,y);break;
		case 'I':tmp=0;insert(rt,x-1,y);if(tmp){tmp=0;rebuild(rt);}break;
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值