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
(优化)

原创粉丝点击