jzoj4216. 【NOIP2015模拟9.12】平方和(splay+码量)

44 篇文章 0 订阅
14 篇文章 0 订阅

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的左儿子为Y
end;

右旋

跟左旋类似,不多说。

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值