BZOJ 3065: 带插入区间K小值 替罪羊树套权值线段树 详解

3065: 带插入区间K小值

Time Limit: 60 Sec  Memory Limit: 512 MB
Submit: 3617  Solved: 1173
[Submit][Status][Discuss]

Description

从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i]。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间k小值。他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少。
这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问。
这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。
这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。(orz 主席树)
这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间k小值在线查询。

Input

第一行一个正整数n,表示原来有n只跳蚤排成一行做早操。
第二行有n个用空格隔开的非负整数,从左至右代表每只跳蚤的弹跳力。
第三行一个正整数q,表示下面有多少个操作。
下面一共q行,一共三种操作对原序列的操作:(假设此时一共m只跳蚤)
1. Q x y k: 询问从左至右第x只跳蚤到从左至右第y只跳蚤中,弹跳力第k小的跳蚤的弹跳力是多少。
    (1 <= x <= y <= m, 1 <= k <= y - x + 1)
2. M x val: 将从左至右第x只跳蚤的弹跳力改为val。
    (1 <= x <= m)
3. I x val: 在从左至右第x只跳蚤的前面插入一只弹跳力为val的跳蚤。即插入后从左至右第x只跳蚤是我刚插入的跳蚤。
    (1 <= x <= m + 1)

为了体现在线操作,设lastAns为上一次查询的时候程序输出的结果,如果之前没有查询过,则lastAns = 0。
则输入的时候实际是:
Q _x _y _k ------> 表示 Q _x^lastAns _y^lastAns _k^lastAns
M _x _val  ------> 表示 M _x^lastAns _val^lastAns
I _x _val  ------> 表示 I _x^lastAns _val^lastAns
简单来说就是操作中输入的整数都要异或上一次询问的结果进行解码。

(祝Pascal的同学早日转C++,就不提供pascal版的描述了。)

Output

对于每个询问输出回答,每行一个回答。

Sample Input

10
10 5 8 28 0 19 2 31 1 22
30
I 6 9
M 1 11
I 8 17
M 1 31
M 6 26
Q 2 7 6
I 23 30
M 31 7
I 22 27
M 26 18
Q 26 17 31
I 5 2
I 18 13
Q 3 3 3
I 27 19
Q 23 23 30
Q 5 13 5
I 3 0
M 15 27
Q 0 28 13
Q 3 29 11
M 2 8
Q 12 5 7
I 30 19
M 11 19
Q 17 8 29
M 29 4
Q 3 0 12
I 7 18
M 29 27

Sample Output

28
2
31
0
14
15
14
27
15
14

HINT

此题作为一个小小的研究来搞吧~做法有很多~不知道这题究竟有多少种做法。
请自觉O(log^2n),我故意卡块状链表,块链A了的请受我深情一拜……
A掉的同学请在Discuss里面简要说下自己的做法吧~
原序列长度 <= 35000
插入个数 <= 35000,修改个数 <= 70000,查询个数 <= 70000  ,0 <= 每时每刻的权值 <= 70000
由于是OJ上的题,所以数据无梯度。为了防止卡OJ,本题只有4组数据。


为了锻炼代码力。。。为了熟练掌握替罪羊树。。。

写了这道BZOJ 3065: 带插入区间K小值 觉得这应该算是好题??


没有插入的话可以用树状数组套主席树开心水过

可是这个插入真是很。。。


好了不扯淡 说题解


我们考虑需要维护的东西,显然是区间和权值

区间用线段树就很GG了,所以我们的选择应该只能是平衡树

而我们又要维护权值信息 权值线段树无疑是棒棒的选择

所以最后就是平衡树内套权值线段树


先来考虑插入,如果用treap splay的话需要旋转,用来直接维护序列关系似乎很不靠谱 每次旋转后的更改信息都不太好搞

所以考虑用更忧的方法——令平衡树不旋转

这时候就充分展现了scapegoat tree的优越性 跑的快 又不用旋转(就是代码稍微长一丢丢)

因为不用旋转,采用暴力重构的方式

所以可以较好的控制信息的合并

BJ代码中采用的是暴力重构后在每个节点中暴力添加每个线段树中应有的元素

所以每次暴力重构的复杂度显然是nlog^2n

每个节点期望重构logn次

所以重构复杂度就是nlog^3n


询问就简单的多了

在平衡树中找出区间对应的所有线段树和节点(不理解看代码)

之后二分就行了

复杂度qlog^2n


所以最后就是nlog^3n


那么单从这种平衡树内套权值线段树的解法来看 是不是不能令复杂度更忧了呢?

我们再来考虑重构的复杂度

每次重构都是nlog^2n,这可真不优美

怎么优化掉呢?

嗯 线段树合并!(合并n个只有单一权值的线段树 复杂度是nlogn的)

所以优化完毕 哈哈

不过由于代码会长一点 懒惰的BJ就采用了时间复杂度换代码复杂度的高端方法。。。。


代码输出调试有点多 不删了。。我好弱。

#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<map>
#include<set>
using namespace std;

typedef double db;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
	return x*f;
}

void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=70010,M=10000100;
const db alpha=0.75;

int n,root,rt[N];

struct scapegoat_tree{int fa,s[2],val;}tr[N];

struct seg_tree{int ls,rs,w;}str[M];

int rec[M],btop,node;

inline int newnode()
{return btop?rec[btop--]:++node;}

void recycle(int &k)
{//cout<<k<<endl;
	if(!k)return ;
	rec[++btop]=k;
	recycle(str[k].ls);recycle(str[k].rs);
	str[k].w=0;k=0;
}

void insert(int &k,int l,int r,int x,int val)
{
	if(!k)k=newnode();
	str[k].w+=val;//cout<<k<<" "<<l<<" "<<r<<" "<<x<<" "<<val<<" "<<str[k].w<<endl;
	if(!str[k].w){recycle(k);return ;}
	if(l==r)return ;int mid=(l+r)>>1;
	x<=mid?insert(str[k].ls,l,mid,x,val):insert(str[k].rs,mid+1,r,x,val);
}

inline bool balance(int k)
{return (db(alpha*str[rt[k]].w)>db(str[rt[tr[k].s[0]]].w))&&(db(alpha*str[rt[k]].w)>db(str[rt[tr[k].s[1]]].w));}

int sta[N],top;

void seek(int k)
{if(!k)return ;recycle(rt[k]);seek(tr[k].s[0]);sta[++top]=k;seek(tr[k].s[1]);}

int build(int l,int r)
{
	if(l>r)return 0;
	int mid=(l+r)>>1,now=sta[mid];//cout<<l<<" "<<r<<" "<<now<<endl;
	tr[now].s[0]=build(l,mid-1);
	tr[now].s[1]=build(mid+1,r);
	tr[tr[now].s[0]].fa=tr[tr[now].s[1]].fa=now;
	for(int i=l;i<=r;++i)insert(rt[now],0,N,tr[sta[i]].val,1);//,cout<<now<<" "<<tr[sta[i]].val<<" : ";cout<<endl;
	return now;
}

void rebuild(int k)
{//cout<<k<<endl;cout<<"SDFSDFSDFSDFSDFSFDFDSSFDSDFDSFDF"<<endl;
	top=0;seek(k);
	int fa=tr[k].fa,son=(tr[fa].s[1]==k),now=build(1,top);
	tr[fa].s[son]=now;tr[now].fa=fa;
	if(k==root)root=now;
}

void insert(int &k,int x,int y,int fa)
{
	if(!k)
	{
		k=++n;
		tr[k].val=y;tr[k].fa=fa;
		int inv=0,tmp=fa;
		while(tmp){if(!balance(tmp))inv=tmp;tmp=tr[tmp].fa;}
		inv?rebuild(inv):insert(rt[k],0,N,y,1);
		return ;
	}
	insert(rt[k],0,N,y,1);
	int tmp=str[rt[tr[k].s[0]]].w;
	x<=tmp?insert(tr[k].s[0],x,y,k):insert(tr[k].s[1],x-tmp-1,y,k);
}

int modify(int k,int x,int y)
{//cout<<k<<" "<<x<<" "<<y<<"    FDAS"<<endl;if(!k)exit(0);
	insert(rt[k],0,N,y,1);
	int son=str[rt[tr[k].s[0]]].w;
	if(x==son+1)
	{
		int tmp=tr[k].val;
		tr[k].val=y;
		insert(rt[k],0,N,tmp,-1);//cout<<tmp<<" "<<y<<"  DGSF"<<endl;
		return tmp;
	}
	int tmp=(x<=son?modify(tr[k].s[0],x,y):modify(tr[k].s[1],x-son-1,y));//cout<<tmp<<endl;
	insert(rt[k],0,N,tmp,-1);
	return tmp;
}

int s[N],p[N],stop,ptop;

void find_seg(int k,int l,int r)
{
	int ls=tr[k].s[0],rs=tr[k].s[1];
	if(l==1&&r==str[rt[k]].w){s[++stop]=rt[k];return ;}
	if(l<=str[rt[ls]].w+1&&r>=str[rt[ls]].w+1){p[++ptop]=k;}
	if(r<=str[rt[ls]].w)find_seg(ls,l,r);
	else if(l>str[rt[ls]].w+1)find_seg(rs,l-str[rt[ls]].w-1,r-str[rt[ls]].w-1);
	else 
	{
		if(l<=str[rt[ls]].w)find_seg(ls,l,str[rt[ls]].w);
		if(r>str[rt[ls]].w+1)find_seg(rs,1,r-str[rt[ls]].w-1);
	}
}

int query(int x,int y,int k)
{
	ptop=stop=0;find_seg(root,x,y);
	register int l=0,r=N,mid,i,sum;//for(i=1;i<=ptop;++i)cout<<tr[p[i]].val<<" ";cout<<endl<<endl;
	while(l<r)
	{
		mid=(l+r)>>1;sum=0;
		for(i=1;i<=stop;++i)sum+=str[str[s[i]].ls].w;
		for(i=1;i<=ptop;++i)sum+=(tr[p[i]].val<=mid&&tr[p[i]].val>=l);//cout<<l<<" "<<r<<" "<<mid<<" "<<sum<<endl;
		if(sum<k)
		{
			for(i=1;i<=stop;++i)s[i]=str[s[i]].rs;
			k-=sum;l=mid+1;
		}
		else 
		{
			for(i=1;i<=stop;++i)s[i]=str[s[i]].ls;
			r=mid;
		}
	}
	return l;
}

int main()
{
	n=read();
	register int i,k,ans=0,x,y;
	for(i=1;i<=n;++i){tr[i].val=read();tr[i].fa=i-1;tr[i-1].s[1]=i;}
	if(n){root=1;rebuild(root);}//cout<<"SFD"<<endl;
	
	int Q=read();
	char opt[2];
	while(Q--)
	{
		scanf("%s",opt);
		x=read()^ans;y=read()^ans;//cout<<str[rt[root]].w<<"  DFSSDF"<<endl;
		switch(opt[0])
		{
			case 'Q':k=read()^ans;ans=query(x,y,k);print(ans);puts("");break;
			case 'M':modify(root,x,y);break;
			case 'I':insert(root,x-1,y,0);break;
		}//for(i=1;i<=n;++i)cout<<tr[i].val<<" "<<tr[i].s[0]<<" "<<tr[i].s[1]<<" "<<str[rt[i]].w<<endl;
	}
	return 0;
}
/*
10
10 5 8 28 0 19 2 31 1 22
30
I 6 9
M 1 11
I 8 17
M 1 31
M 6 26
Q 2 7 6
I 23 30
M 31 7
I 22 27
M 26 18
Q 26 17 31
I 5 2
I 18 13
Q 3 3 3
I 27 19
Q 23 23 30
Q 5 13 5
I 3 0
M 15 27
Q 0 28 13
Q 3 29 11
M 2 8
Q 12 5 7
I 30 19
M 11 19
Q 17 8 29
M 29 4
Q 3 0 12
I 7 18
M 29 27

28
2
31
0
14
15
14
27
15
14
*/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值