【BZOJ】3065: 带插入区间K小值

题意:带插入、修改的区间k小值在线查询。(原序列n<=35000, 询问<=175000)

#include <bits/stdc++.h>
using namespace std;

const int nTr=1000005, nSg=15000005, alphaA=4, alphaB=5;

int STop;
struct Seg *Snull;
struct Seg { Seg *l, *r; int s, cnt; }Sg[nSg], *iSg=Sg, *bin[nSg];
Seg *newS() {
	Seg *x;
	if(!STop) x=iSg++;
	else x=bin[STop--];
	x->l=x->r=Snull; x->s=0; x->cnt=1;
	return x;
}
void clean(Seg *&x) {
	if(x!=Snull && (--x->cnt==0)) {
		bin[++STop]=x;
		clean(x->l); clean(x->r);
	}
	x=Snull;
}
Seg *merge(Seg *l, Seg *r) {
	if(l==Snull) { ++r->cnt; return r; }
	if(r==Snull) { ++l->cnt; return l; }
	Seg *p=newS();
	p->l=merge(l->l, r->l);
	p->r=merge(l->r, r->r);
	p->s=l->s+r->s;
	return p;
}
Seg *ins(Seg *p, int k, int _s, int l, int r) {
	if(p->s+_s==0) { ++Snull->cnt; return Snull; }
	int mid=(l+r)>>1;
	Seg *x=newS();
	x->l=p->l; ++x->l->cnt;
	x->r=p->r; ++x->r->cnt;
	x->s=p->s+_s;
	if(l==r) return x;
	if(k<=mid) { --x->l->cnt; x->l=ins(p->l, k, _s, l, mid); }
	else { --x->r->cnt; x->r=ins(p->r, k, _s, mid+1, r); }
	return x;
}
Seg *ins(Seg *p, int k, int s) {
	Seg *x=ins(p, k, s, 0, 70000);
	clean(p);
	return x;
}
struct node *null;
struct node {
	node *c[2];
	int s, k;
	Seg *t;
	void pushup() { s=c[0]->s+c[1]->s+1; t=ins(merge(c[0]->t, c[1]->t), k, 1); }
}Tr[nTr], *iTr=Tr, *root;
node* newT(int k) {
	node *x=iTr++;
	x->s=1; x->k=k; x->c[0]=x->c[1]=null; x->t=Snull; ++Snull->cnt;
	return x;
}
node* flatten(node *x, node *y) {
	if(x==null) return y;
	clean(x->t);
	x->c[1]=flatten(x->c[1], y);
	return flatten(x->c[0], x);
}
node* build(node *x, int n) {
	if(n==0) { x->c[0]=null; return x; }
	node *y=build(x, n>>1);
	node *z=build(y->c[1], n-(n>>1)-1);
	y->c[1]=z->c[0];
	z->c[0]=y;
	y->pushup();
	return z;
}
void rebuild(node *&x) {
	static node y;
	node *head=flatten(x, &y);
	build(head, x->s);
	x=y.c[0];
}
node *rB;
void insert(node *&x, int pos, int val) {
	if(x==null) { x=newT(val); x->t=ins(Snull, val, 1); return; }
	++x->s;
	x->t=ins(x->t, val, 1);
	int s=x->c[0]->s;
	if(pos<=s) insert(x->c[0], pos, val);
	else insert(x->c[1], pos-s-1, val);
	if(max(x->c[0]->s, x->c[1]->s)*alphaB<=x->s*alphaA) { if(rB!=null) rebuild(x->c[rB==x->c[1]]), rB=null; }
	else rB=x;
}
int update(node *x, int pos, int val) {
	x->t=ins(x->t, val, 1);
	int s=x->c[0]->s+1;
	if(pos==s) swap(val, x->k);
	else if(pos<s) val=update(x->c[0], pos, val);
	else val=update(x->c[1], pos-s, val);
	x->t=ins(x->t, val, -1);
	return val;
}
struct Link {
	int len;
	struct Ln { int l, r; Seg *t; }val[nTr<<2];
	void add(Seg *x) { if(x==Snull) return; ++len; val[len].t=x; val[len].l=len-1; val[len].r=0; val[len-1].r=len; }
	void del(int x) { val[val[x].l].r=val[x].r; val[val[x].r].l=val[x].l; }
	void clr() { len=0; val[0].l=val[0].r=0; }
};
void query(node *x, int R, Link &a, Link &b) {
	if(x==null || !R) return;
	int s=x->c[0]->s+1;
	if(s<=R) a.add(x->t), b.add(x->c[1]->t), query(x->c[1], R-s, a, b);
	else query(x->c[0], R, a, b);
}
int last;
Link a, b;
void Query(int x, int y, int k) {
	a.clr(); b.clr();
	query(root, x-1, b, a);
	query(root, y, a, b);
	int l=0, r=70000;
	while(l<r) {
		x=a.val[0].r, y=b.val[0].r;
		int sum=0;
		while(x) sum+=a.val[x].t->l->s, x=a.val[x].r;
		while(y) sum-=b.val[y].t->l->s, y=b.val[y].r;
		x=a.val[0].r, y=b.val[0].r;
		if(k<=sum) {
			r=(l+r)>>1;
			while(x) { a.val[x].t=a.val[x].t->l; if(a.val[x].t==Snull) a.del(x); x=a.val[x].r; }
			while(y) { b.val[y].t=b.val[y].t->l; if(b.val[y].t==Snull) b.del(y); y=b.val[y].r; }
		}
		else {
			l=((l+r)>>1)+1;
			k-=sum;
			while(x) { a.val[x].t=a.val[x].t->r; if(a.val[x].t==Snull) a.del(x); x=a.val[x].r; }
			while(y) { b.val[y].t=b.val[y].t->r; if(b.val[y].t==Snull) b.del(y); y=b.val[y].r; }
		}
	}
	printf("%d\n", last=l);
}
void Update(int x, int val) {
	update(root, x, val);
}
void Insert(int x, int val) {
	rB=null;
	insert(root, x, val);
	if(rB!=null) rebuild(root);
}
void build(node *&x, int l, int r) {
	if(l>r) return;
	int mid=(l+r)>>1;
	x=newT(-1);
	build(x->c[0], l, mid-1);
	scanf("%d", &x->k);
	build(x->c[1], mid+1, r);
	x->pushup();
}
void init() {
	Snull=new Seg; Snull->l=Snull->r=Snull; Snull->s=0;
	null=new node; null->s=null->k=0; null->t=Snull; null->c[0]=null->c[1]=null;
	root=null;
}
int main() {
	init();
	int n, x, y, k; scanf("%d", &n);
	build(root, 1, n);
	scanf("%d", &n);
	char s[5];
	while(n--) {
		scanf("%s", s); scanf("%d%d", &x, &y); x^=last, y^=last;
		if(s[0]==‘Q‘) scanf("%d", &k), k^=last, Query(x, y, k);
		else if(s[0]==‘M‘) Update(x, y);
		else Insert(x-1, y);
	}
	return 0;
}

  

吐槽:妈呀,这题我写了12个小时.................................................................................................................................................................还是期末考试后10天内A的第一道题...我是有多颓废....

afo的节奏啊.............................................................

题解:

本题我自己yy出一种做法,线段树套splay...然后发现vfk的blog已经有这种做法...而且还说明会tle.............................所以果断没写了.....大概就是线段树维护权值,splay维护区间,插入的时候直接从线段树根走到叶子均插入区间到splay中,然后将原序列后边的值在splay里打个tag表示向后移一步...(或者还有更简单的?),修改就是先删除后插入,查询在线段树二分权值即可....

然后vfk写了好多份题解QAQ...我只是为了学替罪羊好去a掉紫荆花之恋这题..没想到颓废这一题就颓了12h...........................

原因在哪..............RERERERERERERERERERERERE............函数式线段树写跪了..............然后大概调了10小时........(第一次写引用计数啊...而且出了好多奇葩问题...)跪烂那些时间排名靠前的做法orzzzzzzzzzzzz

替罪羊树:详细看vfk论文...我就说说大概...............

插入:如果子树的size>当前根的size*alpha,那么就暴力重建 = =...可以证明复杂度均摊O(lgn)....(在论文里还有个深度平衡...其实没必要= =...学习hzwer神犇的姿势...我们只需要找到第一个大小平衡的根然后找到对应子女是大小不平衡的进行重建即可...

删除:bst一样........(好久没写过的样子..........似乎也是当子树那啥然后暴力重建....

无旋转....

所有操作复杂度都是O(lgn)的..

这里有一种很神的重建技巧...使其空间保持在O(lgn)内..(可能常数大了不少 = =)

这个操作叫拍扁(排便)23333333333333333333333

先将子树转化为链表...(炒鸡简单...定义flatten(x,y)返回x及其子树最前边的那个,且最后边后边加入一个y,则

if(x==null) return y; right(x)=flatten(right(x), y); return flatten(left(x), x);

是不是炒鸡容易理解...(递归定义啊亲..

然后是重建...(也是炒鸡简单,只不过绕了一下..定义build(x,n)表示返回当前子树的假根(是x开头长度为n的链表最后一个元素的后一个元素,不是要求的根),子树x都在这个假根的左子女中,且左子女大小为n

那么

if(n==0) { left(x)=null; return x; }
node *y=build(x, n/2);
node *z=build(x, n-n/2-1);
right(y)=left(z): //后边序列为右子树,其实真正得到的根是y而不是z,而由定义知道z是后边序列的假根,所以假根的左子女包含了所有的原列表的元素
left(z)=y; //定义得出
return z;

然后这就是拍扁....

最后是插入的一个技巧,请看代码....

然后进入正题..(妈呀,果然垃圾回收是正题吗...

首先膜拜hzwer的垃圾回收,因为我没看懂QAQ,为什么在函数式插入那里竟然直接修改了!!!(@hzwer

然后膜拜vfk的垃圾回收,恩.我选择的就是这种..............引用计数.................

引用计数..由名字可以看出是由引用决定了当前元素是否存在的.....

那么我们维护一个cnt值,表示当前元素被多少所有存在的元素引用(注意一定是存在的元素,即不是引用,例如x是元素,node *y=x,y只能算一种引用而不是元素)

真正的元素一定是先new了一个节点,然后更改的那个。

因此我们发现每一次new的时候一定是被别的元素给引用过了...所以cnt=1

每一次插入的时候,如果最终的size=0,说明这个元素已经无用...给null的cnt+1,返回null(即表示用ins赋值的那个元素引用了null

然后其实我们ins递归定义是返回一个被引用的指针,即cnt是要被++的

随便搞搞就行辣...

接下来是两棵函数式线段树合并...递归定义merge(a,b)返回一棵合并了a和b的新元素(或者是其中之一的引用)

首先如果有一棵是null,那么直接返回另一棵的元素,即引用,所以cnt要++

而如果都不是null,由于我们是函数式...从不修改任何东西..于是我们就new一个元素....然后大小为两棵线段树的大小之和,然后递归定义我们左右子女的引用....

然后就好辣...

最后是垃圾回收....如果cnt为0的时候就要去掉....

唔?你问我什么时候调用回收?当然是一个新的元素的引用少了一个时调用啦...比如说t=ins(t, a, b); 其实t原来指向的元素少了t这个东西引用,,,因此要这样p=t; t=ins(p, a, b); clean(p);因为t没有引用原来引用的东西辣....

然后写完后喜闻乐见的时间垫底了...............

时间: 2024-10-19 13:28:29

【BZOJ】3065: 带插入区间K小值的相关文章

bzoj 3065: 带插入区间K小值 替罪羊树 &amp;&amp; AC300

3065: 带插入区间K小值 Time Limit: 60 Sec  Memory Limit: 512 MBSubmit: 1062  Solved: 253[Submit][Status] Description 从 前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一 下,查询区间k小值.他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少. 这可难不倒伏特,

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

题目大意:带插入,单点修改的区间k小值在线查询. 思路:本年度做过最酸爽的题. 树套树的本质是一个外层不会动的树来套一个内层会动(或不会动)的树.两个树的时间复杂度相乘也就是差不多O(nlog^2n)左右.但是众所周知,高级数据结构经常会伴有庞大的常数,所以一般来说树套树的常数也不会小到哪去.所以在做这种题的时候先不要考虑常数的问题... 为什么要用替罪羊树呢?因为一般的平衡树都是会动的,这就很难办了.外层的树动了之后,内层的树肯定也是会动的.很显然,一般的二叉平衡树会经常会旋转,这样在动外层的

bzoj 3065 带插入区间k小值

替罪羊树套权值线段树. 计数式垃圾回收. 复杂度nlog2^n. 写了半个冬令营. 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<vector> 5 #include<algorithm> 6 #define N 10000005 7 #define alpha 0.75 8 using namespace std; 9 inline int read

[BZOJ3065]带插入区间K小值 解题报告 替罪羊树+值域线段树

刚了一天的题终于切掉了,数据结构题的代码真**难调,这是我做过的第一道树套树题,做完后感觉对树套树都有阴影了......下面写一下做题记录. Portal Gun:[BZOJ3065]带插入区间k小值. 这道题的题面其实都提醒怎么做了,维护区间k小值用值域线段树,但要维护一个插入操作,树状数组套主席树也用不了,那么这道题还剩下平衡树可以搞,那就上平衡树吧. 我这里的做法,因为要维护序列的顺序,所以我这里用到替罪羊树套值域线段树:我们在替罪羊树的每个节点都套一颗值域线段树,记录以该节点为根的子树的

Bzoj3065 带插入区间K小值

Time Limit: 60 Sec  Memory Limit: 512 MBSubmit: 3436  Solved: 1103 Description 从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一下,查询区间k小值.他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少.这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问.这时伏

【bzoj3065】带插入区间K小值 替罪羊树套权值线段树

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

【块状链表】【权值分块】bzoj3065 带插入区间K小值

显然是块状链表的经典题.但是经典做法的复杂度是O(n*sqrt(n)*log(n)*sqrt(log(n)))的,出题人明确说了会卡掉. 于是我们考虑每个块内记录前n个块的权值分块. 查询的时候差分什么的,复杂度就是O(n*sqrt(n))的了. 插入的时候为了防止块过大,要考虑裂块(细节较多). 感谢bzoj提供O2,我的STL块链才能通过(list+vector). #include<cstdio> #include<list> #include<vector> #

bzoj3065带插入区间K小值

这题其实好像很难,但是听werkeytom_ftd说可以用块链水,于是就很开心地去打了个块状链表套主席树,插入操作就直接插到一个块中,注意如果块的大小2*block就将块分开,注意每一个修改或插入都要修改后继的状态,贴代码: #include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<cstring> #define fo(i,a,b) for(

POJ 2761(求区间第k小值)

Feed the dogs Time Limit: 6000MS   Memory Limit: 65536KB   64bit IO Format: %I64d & %I64u Submit Status Description Wind loves pretty dogs very much, and she has n pet dogs. So Jiajia has to feed the dogs every day for Wind. Jiajia loves Wind, but no