毒瘤数据结构之瑟尼欧里斯树

Posted yzhang-rp-inf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了毒瘤数据结构之瑟尼欧里斯树相关的知识,希望对你有一定的参考价值。

一、什么是瑟尼欧里斯树

什么是瑟尼欧里斯树?(大雾)

口胡珂学数据结构?

瑟尼欧里斯树是为了纪念珂朵莉而诞生的,因为有了珂朵莉树,所以只能叫瑟尼欧里斯树(大雾)

瑟尼欧里斯树主要资瓷以下两种操作:

1.把区间【l,r】里大于/小于(等于)x的数减去/加上x

2.求区间【l,r】里有多少数等于x

二、瑟尼欧里斯树啥时出现

No1.CF896E Welcome home, Chtholly

链接:https://www.luogu.org/problemnew/show/CF896E

做法简介:板子题

No2.YNOI2018 五彩斑斓的世界

链接:https://www.luogu.org/problemnew/show/P4117

做法简介:板子+卡常

三、瑟尼欧里斯树的具体思想

瑟尼欧里斯树实际就是分块,只是块的形式和普通的分块不同。

维护时,每个块内,维护这这个块中每一种不同的数字的首地址,其他的数用并查集连到首地址中,减的操作就可以直接用lazy_tag打到首地址上。(边块直接暴力,一个一个扫描)

查询时,在所对应的块中扫描首地址暴力就行了。(边块时更暴力,一个一个扫描)

瑟尼欧里斯树和珂朵莉树很像,都是把相同的数存在一起,只是维护的方式稍有不同。

四、瑟尼欧里斯树的程序实现

注解足够,没什么好讲的。

#pragma GCC optimize("-O3") //手开O3 
#include<bits/stdc++.h>
#define sight(c) (‘0‘<=c&&c<=‘9‘)
#define SIZ 254
#define OTK 407
#define Re register
#define N 100607
#define fp puts
#define l(x) (x*SIZ-SIZ+1)
#define r(x) (x*SIZ)
inline void read(int &x){
    static char c;
    for (c=getchar();!sight(c);c=getchar());
    for (x=0;sight(c);c=getchar())x=x*10+c-48;
}
void write(int x){
    if (x<10) {putchar(‘0‘+x); return;} 
    write(x/10); 
    putchar(‘0‘+x%10);
}
inline void writeln(int x){ 
    if (x<0) putchar(‘-‘),x*=-1; 
    write(x); 
    putchar(‘
‘); 
}
using namespace std;
struct RR{
    int num,root;
}V[OTK][N];
int pre[N],pos[N],a[N],ma[N],n,m,be[N],op,l,r,v,p,q,ans,lz[N];
inline int fa(int x){
    //找节点的首地址 
    while (x^pre[x]) 
        x=pre[x]=pre[pre[x]];  
    return x;
}
inline void push(int x) {
    //取消懒标记 
    for (Re int i=l(x);i<=r(x);i++) 
    {
        a[i]=pos[fa(i)];
        V[x][a[i]].root=V[x][a[i]].num=0;
        a[i]-=lz[x];
    }
    for (int i=l(x);i<=r(x);i++) 
        pre[i]=0; 
    lz[x]=0;
}
inline void reb(int x){
    ma[x]=0;
    for (Re int i=l(x);i<=r(x);i++) //l(x),r(x)表示块的左右 
    {
        if (a[i]>ma[x]) 
            ma[x]=a[i]; //找最大 
        V[x][a[i]].root?pre[i]=V[x][a[i]].root:(pos[i]=a[i],V[x][a[i]].root=i,pre[i]=i);  
        //判定是否有祖先(首地址),如果有,就并入首地址的数组(并查集),如果没有,就新开一个首地址 
        V[x][a[i]].num++;   //首地址表示相同的数的数量加上新进来的 
    }
}
RR *A,*B;
void play(int x,int a,int b){
    A=&V[x][a]; 
    B=&V[x][b];
    B->root?pre[A->root]=B->root:(B->root=A->root,pos[A->root]=b);
    B->num+=A->num;
    A->num=A->root=0;
}
inline void det(int x,int v){
    //加懒标记 
    int &p=lz[x] ,&q=ma[x];
    if ((v<<1)<=q-p) 
    {
        for (Re int i=p+1;i<=p+v;i++)
            if (V[x][i].root) 
                play(x,i,i+v);
        p+=v;
    } 
    else 
    {
        for (Re int i=q;i>p+v;i--)
            if (V[x][i].root) 
                play(x,i,i-v);
        q=min(q,p+v);
    }
}
int main () {
    //读入 
    read(n); 
    read(m);
    for (Re int i=1;i<=n;i++) 
    {
        read(a[i]);
        be[i]=(i-1)/SIZ+1;  //标记每个点分别属于哪一个块 
    }
    for (Re int i=1;i<=be[n];i++) 
        reb(i); //初始化 
    while (m--) {
        //读入 
        read(op); 
        read(l); 
        read(r); 
        read(v);
        switch (op) {
            case 1:     //维护操作 
                p=be[l];    //l所在的块(区间第一块) 
                q=be[r];    //r所在的块( 区间最后一块) 
                if (p^q)    //如果p不等于q(区间包含多个块) 
                {
                    push(p);    //把左右两个暴力做的边块的lazy_tag给push_down(把懒标记暴力去除) 
                    push(q);
                    for (Re int i=l;i<=r(p);i++)    //最左边的块暴力一个一个减 
                        if (a[i]>v) 
                            a[i]-=v;
                    for (Re int i=l(q);i<=r;i++)    //最右边的块暴力一个一个减 
                        if (a[i]>v) 
                            a[i]-=v;
                    for (Re int i=p+1;i<q;i++)    //中间的直接打到lazy_tag上 
                        det(i,v);
                    reb(p);    //更新左右块内的情况 
                    reb(q);
                } 
                else {
                    push(p);    //暴力把块的lazy_tag给push_down(把懒标记暴力去除)
                    for (Re int i=l;i<=r;i++)   //暴力减 
                        if (a[i]>v) 
                            a[i]-=v;
                    reb(p); //更新情况 
                }
                break;
            case 2: //查询操作 
                p=be[l];    //l所在的块(区间第一块) 
                q=be[r];    //r所在的块( 区间最后一块) 
                ans=0;
                if (p^q)    //如果p不等于q(区间包含多个块) 
                {
                    for (Re int i=l;i<=r(p);i++)    //最左边的块暴力一个一个加进答案 
                        if (pos[fa(i)]-lz[p]==v) 
                            ans++;
                    for (Re int i=l(q);i<=r;i++)    //最右边的块暴力一个一个加进答案 
                        if (pos[fa(i)]-lz[q]==v) 
                            ans++;
                    for (Re int i=p+1;i<q;i++)      //中间的块利用首地址加进答案 
                        if (v+lz[i]<N) 
                            ans+=V[i][v+lz[i]].num; 
                }
                else 
                    for (Re int i=l;i<=r;i++)   //这段的内容暴力加入答案  
                        if (pos[fa(i)]-lz[p]==v) 
                            ans++;
                writeln(ans);   //输出 
                break;
        }
    }
    return 0; 
}

实际珂朵莉式卡常也可以(大雾)

以上是关于毒瘤数据结构之瑟尼欧里斯树的主要内容,如果未能解决你的问题,请参考以下文章

毒瘤数据结构之奈芙莲树

二叉树遍历高级算法之Morris---莫里斯算法

数据结构平衡树treap

毒瘤数据结构之珂朵莉树

1004F.Sonya and Bitwise OR(毒瘤线段树+分治思想)

二叉数莫里斯遍历