论在LCT上下放标记

对LCT上标记下放的一些研究

  • 实验题目
    Codevs1082线段树练习3

    http://codevs.cn/problem/1082/

  • 实验目的
    探究在LCT上下放标记的方式与特点,并与线段树和平衡树Splay做一下比较。

  • 如何用LCT表示一个数列
    我用了种“猥琐”的方法——各结点前连后建成一条链。
    其实看起来最快的方法是先用O(n)的时间建成一棵平衡树,然后用类似树链剖分中轻重链剖分的办法,把这棵树剖成实链和虚链,每条链再建成平衡的Splay。

  • 如何用LCT提取一个区间
    LCT以Splay操作为基础,所以其提取区间的方式和Splay类似,而与线段树不同。若要提取区间[x,y],则先把x置为整棵树的根,再对y进行access和Splay操作。这样以后,x和y在一条实链上,并且x是树根保证这条实链没有x之前的结点,access(y)操作保证在Splay中y没有右子结点。所以此时结点y代表了区间[x.y]的一切!此时y对应的所有域(比如对应实链的和)都是区间[x,y]。

  • 如何用LCT对一段区间打上标记
    我们以区间加一个数为例。
    利用上文的办法,我们可以用不多于3行代码提取出所求的区间。对区间打标记可以借鉴NOI2005维护数列一题在Splay上打标记的方法,即直接在y上标记,更改y,然后进行标记下传即可。更改y需要LCT维护一个size域,表示Splay树中子树的大小。当我们用上述方式提取出一段区间[x,y]时,y不多不少地代表了一切。区间[x,y]的结点个数就是size(y)的值。区间加上一个数时,y结点对应的权值加上这个数,y对应的总和加上size(y)和这个数的乘积,然后标记数组加上这个数。

  • 如何在LCT上下传标记
    我们以区间加一个数为例。
    首先,update操作维护size域和sum的值;
    pushdown在下传完反转标记的时候(或之前),下传加标记,此时同时更改子结点的值和子结点的sum值,其中sum的值借助子结点的size域实现更新,然后把标记下传至子结点。

  • LCT上打标记和下放标记的特点
    因为提取区间[x,y]后,y代表了一切,所以区间修改在LCT中体现为对y结点的单点修改。区间下放标记时对子结点的修改和修改y的过程极其类似,所以可以设计成同一个函数。

  • Code

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long ll;
const int N = 200005, nil = 0;

bool rev[N];
int lc[N], rc[N], fa[N], sz[N];
int n, m, S[N];
ll val[N], sum[N], d[N];

inline void update(int);
inline void pushdown(int);
void zig(int);
void zag(int);
void splay(int);
inline int access(int);
inline void makeroot(int);
inline void lnk(int , int);
inline void cut(int , int);
inline ll query(int , int);
inline void add(int , ll);

int main()
{
    scanf("%d", &n);
    int x, y, opt;
    ll z;
    for(int i = 1; i <= n; ++i)
    {
        scanf("%lld", &z);
        access(i); splay(i);
        add(i, z);
    }
    for(int i = 1; i < n; ++i) lnk(i, i + 1);
    scanf("%d", &m);
    while(m--)
    {
        scanf("%d", &opt);
        if(opt == 1)
        {
            scanf("%d%d%lld", &x, &y, &z);
            makeroot(x);
            access(y); splay(y);
            add(y, z);
        }
        else
        {
            scanf("%d%d", &x, &y);
            printf("%lld\n", query(x, y));
        }
    }
    return 0;
}

inline void update(int rot)
{
    sz[rot] = sz[lc[rot]] + sz[rc[rot]] + 1;
    sum[rot] = val[rot] + sum[lc[rot]] + sum[rc[rot]];
}
inline void pushdown(int rot)
{
    if(rev[rot])
    {
        swap(lc[rot], rc[rot]);
        rev[lc[rot]] ^= 1;
        rev[rc[rot]] ^= 1;
        rev[rot] = false;
    }
    if(d[rot] != 0)
    {
        add(lc[rot], d[rot]); add(rc[rot], d[rot]);
        d[rot] = 0;
    }
}
void zig(int x)
{
    int y = fa[x];
    lc[y] = rc[x];
    fa[rc[x]] = y;
    rc[x] = y;
    fa[x] = fa[y];
    if(y == lc[fa[y]]) lc[fa[y]] = x;
    else if(y == rc[fa[y]]) rc[fa[y]] = x;
    fa[y] = x;
    update(y);
}
void zag(int x)
{
    int y = fa[x];
    rc[y] = lc[x];
    fa[lc[x]] = y;
    lc[x] = y;
    fa[x] = fa[y];
    if(y == lc[fa[y]]) lc[fa[y]] = x;
    else if(y == rc[fa[y]]) rc[fa[y]] = x;
    fa[y] = x;
    update(y);
}
void splay(int x)
{
    int top = 0;
    S[top++] = x;
    for(int i = x; i == lc[fa[i]] || i == rc[fa[i]]; i = fa[i])
    {
        S[top++] = fa[i];
    }
    while(top--) pushdown(S[top]);
    int y = nil, z = nil;
    while(x == lc[fa[x]] || x == rc[fa[x]])
    {
        y = fa[x]; z = fa[y];
        if(x == lc[y])
        {
            if(y == lc[z]) zig(y);
            zig(x);
        }
        else
        {
            if(y == rc[z]) zag(y);
            zag(x);
        }
    }
    update(x);
}
inline int access(int x)
{
    int y;
    for(y = nil; x != nil; y = x, x = fa[x])
    {
        splay(x);
        rc[x] = y;
        update(x);
    }
    return y;
}
inline void makeroot(int x)
{
    access(x); splay(x); rev[x] ^= 1;
}
inline void lnk(int x, int y)
{
    makeroot(x);
    fa[x] = y;
}
inline void cut(int x, int y)
{
    makeroot(x);
    access(y); splay(y);
    fa[x] = lc[y] = nil;
    update(y);
}
inline ll query(int x, int y)
{
    makeroot(x);
    access(y); splay(y);
    return sum[y];
}
inline void add(int x, ll mrk)
{
    val[x] += mrk;
    sum[x] += sz[x] * mrk;
    d[x] += mrk;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值