poj 3468 Splay 树

大二上的时候,写过一个AVL的操作演示,今天一看Splay,发现和AVL其实一样,加上线段树的基础,懒惰标记什么都知道,学起来轻松许多哦

我参考的模板来自这里  http://blog.csdn.net/u013480600/article/list/2

里面有大量的ch[r][0] ch[r][1]等 我建议用宏定义取代,写的时候方括号少打了很多,等做的题多得时候,我再把自己使用的模板发来

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

/*===============================
Splay树模板
1、tot1,tot2都是从1开始
2、
================================*/

#define key_value ch[ch[root][1]][0]
#define ls(r) ch[r][0]
#define rs(r) ch[r][1]
#define ll long long
#define repe(i,s,e) for(int i=s;i<=e;i++)
#define rep(i,s,e) for(int i=s;i<e;i++)

const int MAXN=100000 +10;
int pre[MAXN],ch[MAXN][2],sz[MAXN],root,tot1;
//  父节点、左右孩子(0为左,同时0为左旋)、子树规模、根节点、结点数量
int key[MAXN];//结点的值
int add[MAXN];
ll sum[MAXN];//sum[i]=v 以i为root的树的和,
int s[MAXN],tot2;/////??????
//内存池、内存池容量(这题用不到,如果有删除操作,内存不够可以这样
//s中存的是被回收的内存,从后面可以看到,tot2不为0的时候优先使用s[tot2]=x中的
//pre[x],ch[x],size[x]等,
//否则就使用tot1++之后产生的数x的对应位置
int a[MAXN];//初始时的数组,建树时用
int n,q;

void newnode(int &r,int father, int k)//r必须是&
{
    if(tot2)r=s[tot2--];//取得时候tot2--,存++tot2
    else r=++tot1;
    pre[r]=father;
    sz[r]=1;
    key[r]=k;
    add[r]=sum[r]=0;
    ch[r][0]=ch[r][1]=0;
}

void updateadd(int r, int av)
{
    if(!r)return;//??
    add[r]+=av;
    key[r]+=av;
    sum[r]+=(ll)av*sz[r];
}
//通过孩子结点更新父亲结点
void pushup(int r)
{
    sz[r]=sz[ls(r)]+sz[rs(r)]+1;
    sum[r]=sum[ls(r)]+sum[rs(r)]+key[r];
}
//将延迟标记更新到孩子结点
void pushdown(int r)
{
    if(add[r])
    {
        updateadd(ls(r),add[r]);
        updateadd(rs(r),add[r]);
        add[r]=0;
    }
}

//建树区间[l,r],先建立中间结点,再建两端,
//注意和线段树的区别
void build(int &x, int l, int r, int father)
{
    if(l>r)return;
    int mid=(l+r)/2;
    newnode(x, father, a[mid]);
    build(ch[x][0], l, mid-1, x);
    build(ch[x][1], mid+1, r, x);
    pushup(x);
}
//初始化,前后各加一个king结点
void Init()
{
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    root=tot1=tot2=0;
    ls(root)=rs(root)=pre[root]=sz[root]=add[root]=sum[root]=key[root]=0;
    newnode(root, 0, -1);//root'sfatheris -1
    newnode(rs(root),root,-1);//头尾各插入一个空
    build(key_value, 1, n, rs(root));
    pushup(rs(root));
    pushup(root);
}
//旋转,0为左旋,1为右旋 该部分基本固定
void rota(int x, int kind)
{
    int y=pre[x];
    pushdown(y);
    pushdown(x);//必须先把y的标记向下传递,在传x
    ch[y][!kind]=ch[x][kind],pre[ch[x][kind]]=y;
    if(pre[y])//??y的父节点不是root
        ch[pre[y]][ rs(pre[y])==y ]=x;//只能对这句牛逼的代码说声我屮艸芔茻
    pre[x]=pre[y];
    ch[x][kind]=y,pre[y]=x;
    pushup(y);//维护y结点   x节点的信息不用PushUp吗?可以证明这里除了x节点维护的信息不对外,其他所有点信息都正确
    //Push_Up(x);//这个可以不写,详见Crash:运用伸展树解决数列维护问题 论文,但是写了也不会多多少时间消耗
}

void Splay(int r, int goal)//将r调整到goal下面
{
    pushdown(r);// 离开之前把懒惰标记的信息传递
    while(pre[r]!=goal)
    {
        if(pre[pre[r]]==goal)
            rota(r, ch[pre[r]][0]==r);//r在左子树,右旋
        else
        {
            int y=pre[r];
            int kind=ch[pre[y]][0]==y;
            if(ch[y][kind]==r)//之字形旋转
            {//y在其父节点的左子树上,r在y的右子树上
             //或者y在其父节点的右子树,r在y左子树

                rota(r, !kind);
                rota(r, kind);
            }
            else    //一字型旋转
            {
                rota(y, kind);//注意这里是y啊
                rota(r, kind);
            }
        }
    }
    pushup(r);
    if(goal==0)root=r;
}

//得到第k个结点编号
int getkth(int r, int k)
{
    pushdown(r);
    int t=sz[ls(r)]+1;
    if(t==k)return r;
    if(t>k)return getkth(ls(r),k);//在左子树第k个
    else return getkth(rs(r), k-t);//在右子树第k-t个
}
//得到以r为根的第一个结点--最左边的节点编号
int getmin(int r)
{
    pushdown(r);
    while(ls(r))
    {
        r=ls(r);
        pushdown(r);
    }
    return r;
}
//得到以r为根的最后一个结点--最右边的编号
int getmax(int r)
{
    pushdown(r);
    while(rs(r))
    {
        r=rs(r);
        pushdown(r);
    }
    return r;
}
void addv(int l, int r, int d)
{
    Splay(getkth(root,l),0);
    Splay(getkth(root,r+2),root);
    updateadd(key_value, d);
    pushup(rs(root));
    pushup(root);
}

ll Querysum(int l, int r)
{
    Splay(getkth(root, l),0);//第l个点到根结点
    Splay(getkth(root, r+2), root);//第r+2个点到根结点的右孩子
    return sum[key_value];
}

int main()
{
    //freopen("poj3468.txt","r",stdin);
    int q;
    while(~scanf("%d%d", &n, &q))
    {
        Init();
        while(q--)
        {
            char op[30];
            int x,y,z;
            scanf("%s",op);
            if(op[0]=='Q')
            {
                scanf("%d%d",&x,&y);
                printf("%lld\n",Querysum(x,y));
            }
            else
            {
                scanf("%d%d%d",&x,&y,&z);
                addv(x,y,z);
            }
        }
    }
    return 0;
}

poj 3468 Splay 树

时间: 2024-11-03 03:36:41

poj 3468 Splay 树的相关文章

POJ 3468 伸展树建树

A Simple Problem with Integers Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 59628   Accepted: 18180 Case Time Limit: 2000MS Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of

POJ 3468 线段树+lazy标记

lazy标记 Time Limit:5000MS     Memory Limit:131072KB     64bit IO Format:%I64d & %I64u Submit Status Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to

poj 3468 线段树成段更新

http://poj.org/problem?id=3468 A Simple Problem with Integers Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 58132   Accepted: 17704 Case Time Limit: 2000MS Description You have N integers, A1, A2, ... , AN. You need to deal with two k

POJ 3468 线段树(成段更新,区间求和)

题目链接:http://poj.org/problem?id=3468 题意:给定一个数列,每次操作可以是将某区间数字都加上一个相同的整数,也可以是询问一个区间中所有数字的和,对每次询问输出结果. 这个线段树运用了应用了add域优化,每个节点除了用value记录当前节点对应区间元素的和之外,还要用add域记录当前节点对应区间每个元素的增量.这样,没必要每次更新都要更新value更新到最底层每一个点,只需要将增量记录在某父节点的add域中即可,如果下次查询或者更新操作的是该父节点对应区间的子区间,

hdu 1698+poj 3468 (线段树 区间更新)

http://acm.hdu.edu.cn/showproblem.php?pid=1698 这个题意翻译起来有点猥琐啊,还是和谐一点吧 和涂颜色差不多,区间初始都为1,然后操作都是将x到y改为z,注意 是改为z,不是加或减,最后输出区间总值 也是线段树加lazy操作 1 #include<cstdio> 2 using namespace std; 3 struct point { 4 int l,r; 5 int val,sum; 6 }; 7 point tree[400007]; 8

poj 3468 线段树 成段增减 区间求和

题意:Q是询问区间和,C是在区间内每个节点加上一个值 Sample Input 10 51 2 3 4 5 6 7 8 9 10Q 4 4Q 1 10Q 2 4C 3 6 3Q 2 4Sample Output 455915 1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 # include <cmath> 6

POJ 3468 线段树区间求和

线段树区间求和树节点不能只存和,只存和,会导致每次加数的时候都要更新到叶子节点,速度太慢(O(nlogn)).所以我们要存两个量,一个是原来的和nSum,一个是累加的增量Inc. 在增加时,如果要加的区间正好覆盖一个节点,则增加其节点的Inc值,不再往下走,否则要更新nSum(加上本次增量),再将增量往下传,这样更新的复杂度就是O(log(n)). 在查询时,如果待查区间不是正好覆盖一个节点,就将节点的Inc往下带,然后将Inc清0,接下来再往下查询. Inc往下带的过程也是区间分解的过程,复杂

A Simple Problem with Integers poj 3468 多树状数组解决区间修改问题。

A Simple Problem with Integers Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 69589   Accepted: 21437 Case Time Limit: 2000MS Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of

POJ 3468(树状数组的威力)

之前说过这是线段树的裸题,但是当看了http://kenby.iteye.com/blog/962159 这篇题解后我简直震惊了,竟然能如此巧妙地转化为用树状数组来处理,附上部分截图(最好还是进入原网址细细品味): 依照他的思路附上我的代码: 1 #include<cstdio> 2 #include<cstring> 3 #define lowbit(x) ((x)&-(x)) 4 typedef long long LL; 5 const int maxn= 10000