jzoj4216. 【NOIP2015模拟9.12】平方和(splay+码量)
来源:互联网 发布:淘宝进口食品货源 编辑:程序博客网 时间:2024/05/21 19:36
Description
给出一个N个整数构成的序列,有M次操作,每次操作有一下三种:
①Insert Y X,在序列的第Y个数之前插入一个数X;
②Add L R X,对序列中第L个数到第R个数,每个数都加上X;
③Query L R,询问序列中第L个数到第R个数的平方和。
Input
第一行一个正整数N,表示初始序列长度。
第二行N个整数Ai,表示初始序列中的数。
第三行一个正整数M,表示操作数。
接下来M行,每行一种操作。
Output
对于每一个Query操作输出答案。由于答案可能很大,请mod 7459后输出。
Sample Input
5
1 2 3 4 5
5
Query 1 3
Insert 2 5
Query 2 4
Add 5 6 7
Query 1 6
Sample Output
14
38
304
样例解释:
第二次操作后的序列:1,5,2,3,4,5。
第四次操作后的序列:1,5,2,3,11,12。
Data Constraint
30%的数据满足N≤1,000,M≤1,000。
另外20%的数据满足N≤100,000,M≤100,000,且不存在Insert操作。
100%的数据满足N≤100,000,M≤100,000,且Add和Insert操作中|X|≤1000,|Ai|≤1000。
如果这题没有插入操作,可以直接用线段树做。
方法
①线段树预处理出空位,离线 (不讲)
②splay
splay定义
splay其实就是二叉排序树的优化。
二叉排序树:
一种特殊的二叉树,每个子树的左节点的关键字都小于根节点,右节点都大于根节点。
splay的优点
时间空间优化,可以通过旋转来实现。
可以删除/插入/更改值。
旋转
分左旋和右旋两种。
①右旋
②左旋
其实旋转操作很简单。。
左旋:
由图得知,B < X < C < Y < A,
旋转后,树的形状改变,但是性质没有改变。
仍然是B < X < C < Y < A。
因为旋转后,Y变成X的子树。
所以只需让X认Y父为父,让Y认X为父,
因为X的右儿子被Y占据,所以把Y的左儿子设为C。
右旋:
同理。
不同是要把Y的右儿子设为C。
X是新父节点的左/右儿子要根据Y决定。
旋转的卵用
其实splay的核心思想,就是通过旋转,把某个节点转到另一个节点的下方,
从而进行一些更重要的操作。
还是旋转
如果经过一通**(这只是星号)乱转,有可能会变成这样一个情况:
平衡树将会由树退化成链。
这样平衡树的优势——logN的复杂度将会得不到体现。
所以旋转要用更科学的方法。
假设当前节点为X,要把X旋转到Z的下方:
①X的爷爷是Z
两种情况:
直接左旋。
直接右旋。
②X的爷爷不是Z
两种:
一条链:
先旋转X的父节点,再旋转X。
反向同理。
如果直接二次旋X会变慢(10%左右,实测)
原理未知
折角:
二连旋X。
如果旋转父节点,则X会不动。
反向同理。
旋转模板(通用)
(T数组表示树,T[i,1]表示左儿子编号,T[i,2]表示右儿子编号,没有加数据更新):
左旋:
procedure left(x:longint);var y,z:longint;begin y:=f[x];//取得父节点 z:=f[y]; f[x]:=z;//修改父节点 f[y]:=x; if z=0 then root:=x//修改根,本题用不上 else begin if t[z,1]=y then//修改X新父节点的儿子 t[z,1]:=x else t[z,2]:=x; end; if t[x,1]>0 then//修改X左儿子父亲 f[t[x,1]]:=y; t[y,2]:=t[x,1];//修改Y右儿子,设为X的左儿子 t[x,1]:=y````//修改X的左儿子为Yend;
右旋
跟左旋类似,不多说。
procedure right(x:longint);var y,z:longint;begin y:=f[x]; z:=f[y]; f[x]:=z; f[y]:=x; if z=0 then root:=x else begin if t[z,1]=y then t[z,1]:=x else t[z,2]:=x; end; if t[x,2]>0 then f[t[x,2]]:=y; t[y,1]:=t[x,2]; t[x,2]:=y;end;
连旋
procedure splay(x,y:longint);var i:longint;begin if (x=y) or (x=0) then exit; while f[x]<>y do begin if f[f[x]]=y then//单旋 begin if t[f[x],1]=x then right(x) else left(x); end else begin//双旋 if (t[f[f[x]],1]=f[x]) and (t[f[x],1]=x) then//二连右 begin right(f[x]); right(x); end else if (t[f[f[x]],2]=f[x]) and (t[f[x],2]=x) then//二连左 begin left(f[x]); left(x); end else if (t[f[f[x]],1]=f[x]) and (t[f[x],2]=x) then//左右 begin left(x); right(x); end else if (t[f[f[x]],2]=f[x]) and (t[f[x],1]=x) then//右左 begin right(x); left(x); end; end; end;end;
查找操作
通过关键字查找,利用二叉排序树的性质,大的在右,小的在左,一路找下去。
这是个难点。
先想想,如果把每个节点的大小明确地记录下来,那么
每次插入节点过后,原来在节点后的点的编号都要加一。
这样太eggache了
作为高 (gǎo) 级 (jī) 算法的splay,其实早已给出了解决方案。
举个例子:
比如,我要从根节点开始寻找第2小的数(假设是节点2)。
根据二叉排序树的性质可得,根节点左子树的节点都比根节点关键字的值小,右子树的都比根节点大。
设size[i]表示以i为根的子树节点个数。
则根节点是第几小的数就是size[根的左儿子]+1(根据性质可得)。
第几小既是它的关键字。
那么我们只需把查找的数和当前查找到的节点作比较,如果关键字更小就在左子树,更大就在右子树,相同就找到了。
还是刚刚那个例子,由于2<(2+1),所以2在3的左子树上。
因为我们要求的是第几小的数,所以当前节点的右子树和父节点们的右子树们(灰圈)
都没有任何卵用
所以可以直接往下找。
又比如,刚刚那个图,我要求7的位置。
计算后发现,要向右走(同上)。
但是走完以后发现,我们搜索的子树范围减小了,因为前面有比它小的数(红圈),所以要减去(size[左儿子]+1)
其实就是把走之前的节点的关键字减去。
查找模板
function find(x,s:longint):longint;begin if s=size[t[x,1]]+1 then//找到 exit(x) else if s<size[t[x,1]]+1 then//在左子树 exit(find(t[x,1],s)) else exit(find(t[x,2],s-size[t[x,1]]-1));//在右子树(要减去当前关键字)end;
插入操作(1/3)
因为一个**的原因电脑崩溃要重打。。。。
其实很简单(条件是上文提到的所有操作都打对),假设要再X前面插入一个节点,先用splay+find把X-1拖到根,再把X拖到X-1的右儿子。
如果不出意外(各种蜜汁错误)就是这样:
因为X-1和X之间没有其它数,所以此时的X也没有左儿子。
直接在X的左儿子新建节点。
解决了三个操作之一。
区间加(2/3)
直接打lazy-tag标记。
好吧,lazy-tag就是把要加的数先存下来,等到计算到它时再加。
大致同线段树。
再旋转/查找/区间加时下传标记。
每次把L-1拉到根,再把R+1拉到L-1的右子树,那么R+1的左子树就是L~R。
直接在它的左儿子上打标记。
又是三分之一。
询问(3/3)
大同区间加,把L-1拉到根,再把R+1拉到L-1的右子树,那么R+1的左子树就是L~R。
直接输出就行了。
又是旋转
这是很重要的一点。
每次旋转后,X和Y的数据都会有改变。
比如这样:
Y的数据变成右子树+A点数据+自己。
X的数据加上新的Y-A点数据
仔细考虑/测试一下。
否则又是各种蜜汁错误
计算答案
这跟树没有任何关系。
(a+b)^2=a^2+b^2+2ab
Σ(a+b)^2=Σa^2+b^2*size+2bΣa
理解理解。
最终代码
const md=7459;var t:array[0..200000,0..2] of longint; f:array[0..200000] of longint; a:array[0..200000] of longint; size:array[0..200000] of longint; num:array[0..200000] of longint; num2:array[0..200000] of longint; put:array[0..200000] of longint; bj:array[0..200000] of longint; n,m,i,j,k,l,x,y,z,root:longint; ch:char; s:string;procedure look;//查看各种数据,测试用。(可删)var i:longint;begin writeln('Root:',root); for i:=1 to n do writeln('Data:',t[i,0],' Left:',t[i,1],' Right:',t[i,2],' Father:',f[i],' Size:',size[i],' X:',num[i],' X^2:',num2[i],' Lazy BJ:',bj[i]);end;procedure downdata(x:longint);//下传数据begin if bj[x]=0 then exit; if t[x,1]>0 then bj[t[x,1]]:=bj[t[x,1]]+bj[x]; if t[x,2]>0 then bj[t[x,2]]:=bj[t[x,2]]+bj[x]; num2[x]:=(((num2[x]+((bj[x]*bj[x]) mod md)*size[x]) mod md)+2*bj[x]*num[x]) mod md; num[x]:=num[x]+bj[x]*size[x]; t[x,0]:=t[x,0]+bj[x]; bj[x]:=0;end;procedure right(x:longint);//右旋var y,z:longint;begin downdata(x); y:=f[x]; z:=f[y]; f[x]:=z; f[y]:=x; if z=0 then root:=x else begin if t[z,1]=y then t[z,1]:=x else t[z,2]:=x; end; size[y]:=size[t[y,2]]+1+size[t[x,2]];//更新 size[x]:=size[x]+size[y]-size[t[x,2]]; num[y]:=(num[t[y,2]]+t[y,0]+num[t[x,2]]) mod md; num[x]:=(num[x]+num[y]-num[t[x,2]]) mod md; num2[y]:=(num2[t[y,2]]+t[y,0]*t[y,0]+num2[t[x,2]]) mod md; num2[x]:=(num2[x]+num2[y]-num2[t[x,2]]) mod md; if t[x,2]>0 then f[t[x,2]]:=y; t[y,1]:=t[x,2]; t[x,2]:=y;end;procedure left(x:longint);//左旋var y,z:longint;begin downdata(x); y:=f[x]; z:=f[y]; f[x]:=z; f[y]:=x; if z=0 then root:=x else begin if t[z,1]=y then t[z,1]:=x else t[z,2]:=x; end; size[y]:=size[t[y,1]]+1+size[t[x,1]]; size[x]:=size[x]+size[y]-size[t[x,1]]; num[y]:=(num[t[y,1]]+t[y,0]+num[t[x,1]]) mod md; num[x]:=(num[x]+num[y]-num[t[x,1]]) mod md; num2[y]:=(num2[t[y,1]]+t[y,0]*t[y,0]+num2[t[x,1]]) mod md; num2[x]:=(num2[x]+num2[y]-num2[t[x,1]]) mod md; if t[x,1]>0 then f[t[x,1]]:=y; t[y,2]:=t[x,1]; t[x,1]:=y;end;procedure splay(x,y:longint);//各种旋var i:longint;begin if (x=y) or (x=0) then exit; while f[x]<>y do begin if f[f[x]]=y then begin if t[f[x],1]=x then right(x) else left(x); end else begin if (t[f[f[x]],1]=f[x]) and (t[f[x],1]=x) then begin right(f[x]); right(x); end else if (t[f[f[x]],2]=f[x]) and (t[f[x],2]=x) then begin left(f[x]); left(x); end else if (t[f[f[x]],1]=f[x]) and (t[f[x],2]=x) then begin left(x); right(x); end else if (t[f[f[x]],2]=f[x]) and (t[f[x],1]=x) then begin right(x); left(x); end; end; end;end;function find(x,s:longint):longint;//查找begin if t[x,1]>0 then downdata(t[x,1]); if t[x,2]>0 then downdata(t[x,2]); if s=size[t[x,1]]+1 then exit(x) else if s<size[t[x,1]]+1 then exit(find(t[x,1],s)) else exit(find(t[x,2],s-size[t[x,1]]-1));end;begin readln(n); root:=1; t[1,2]:=2; for i:=1 to n do begin read(a[i+1]); f[i+1]:=i;//建树 t[i+1,0]:=a[i+1]; t[i+1,2]:=i+2; size[i+1]:=n+2-i; end; n:=n+2; for i:=n-1 downto 1 do begin num[i]:=(num[i+1]+a[i]) mod md;//初始化 num2[i]:=(num2[i+1]+a[i]*a[i]) mod md; end; size[1]:=n;//个数 size[n]:=1; f[n]:=n-1; readln(m); for i:=1 to m do begin s:=''; read(ch); while ch<>' ' do begin s:=s+ch; read(ch); end; read(x,y); if s[1]='A' then read(z); readln; case s[1] of 'A'://查找 begin j:=find(1,x); k:=find(1,y+2); splay(j,1); splay(k,j); bj[t[k,1]]:=bj[t[k,1]]+z; downdata(t[k,1]); end; 'I'://插入 begin j:=find(1,x); k:=find(1,x+1); splay(j,1); splay(k,j); inc(n); t[n,0]:=y; t[n,1]:=0; t[n,2]:=0; f[n]:=k; t[k,1]:=n; num[n]:=y; num2[n]:=y*y mod md; size[n]:=1; num[j]:=(num[j]+y) mod md; num[k]:=(num[k]+y) mod md; num2[j]:=(num2[j]+y*y) mod md; num2[k]:=(num2[k]+y*y) mod md; inc(size[j]); inc(size[k]); end; 'Q'://询问 begin j:=find(1,x); k:=find(1,y+2); splay(j,1); splay(k,j); writeln((num2[t[k,1]]+md) mod md); end; end; end;end.
参考资料:
http://blog.csdn.net/skydec/article/details/20151805
http://blog.csdn.net/cold_chair/article/details/72152376
http://blog.csdn.net/alan_cty/article/details/51220930
http://blog.csdn.net/Cold_Chair/article/details/71431490
http://blog.csdn.net/lyd_7_29/article/details/54292732
http://blog.csdn.net/jerrydung/article/details/7952460
(优化)
- jzoj4216. 【NOIP2015模拟9.12】平方和(splay+码量)
- jzoj4216. 【NOIP2015模拟9.12】平方和
- [jzoj]4216. 【NOIP2015模拟9.12】平方和(splay)
- 【NOIP2015 模拟9.12】平方和
- 【NOIP2015模拟9.12】平方和
- [jzoj]4216. 【NOIP2015模拟9.12】平方和【线段树二分+码量】
- 4216. 【NOIP2015模拟9.12】平方和
- [JZOJ4216]平方和
- JZOJ4216 平方和
- NOIP2015模拟1
- NOIP2015模拟3
- NOIP2015模拟2
- NOIP2015模拟4
- 【NOIP2015 10.22模拟】总结
- 【NOIP2015模拟10.20】平均数
- 【NOIP2015模拟10.20】ACM
- 【NOIP2015 模拟10.27】总结
- 【NOIP2015 10.29模拟】总结
- 172. Factorial Trailing Zeroes计算n!末尾0的个数
- python3 [爬虫入门实战]爬虫之selenium 安装设置与初步使用
- Nginx全局变量
- C# 连接数据库 以及数据库增删改查
- SpringMVC对静态资源的的处理
- jzoj4216. 【NOIP2015模拟9.12】平方和(splay+码量)
- 正则表达式
- Peewee中文文档【五】:贡献
- (6)用户、组与权限管理
- java实现url转码、解码
- HDOJ 4975 A simple Gaussian elimination problem【最大流Dinic+判环】
- python学习小记
- Android 全局背景音乐
- UNIX网络编程卷1:套接字联网-第4章:基本TCP套接字编程1