线段树

Posted liankewei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线段树相关的知识,希望对你有一定的参考价值。

线段树

九条可怜是一个喜欢数据结构的女孩子,在常见的数据结构中,可怜最喜欢的就是线段树。

线段树的核心是懒标记,下面是一个带懒标记的线段树的伪代码,其中 `tag` 数组为懒标记:

![](https://s2.ax1x.com/2019/04/02/AyHyRJ.md.png)

其中函数 $ exttt{Lson}( ext{Node})$ 表示 $ ext{Node}$ 的左儿子,$ exttt{Rson}( ext{Node})$ 表示 $ ext{Node}$ 的右儿子。

现在可怜手上有一棵 $[1, n]$ 上的线段树,编号为 $1$。这棵线段树上的所有节点的 `tag` 均为 $0$。接下来可怜进行了 $m$ 次操作,操作有两种:
- $1 l r$,假设可怜当前手上有 $t$ 棵线段树,可怜会把每棵线段树复制两份(`tag` 数组也一起复制),原先编号为 $i$ 的线段树复制得到的两棵编号为 $2i − 1$ 与 $2i$,在复制结束后,可怜手上一共有 $2t$ 棵线段树。接着,可怜会对所有编号为奇数的线段树进行一次 $ exttt{Modify}( ext{root}, 1, n, l, r)$。
- $2$,可怜定义一棵线段树的权值为它上面有多少个节点 `tag` 为 $1$。可怜想要知道她手上所有线段树的权值和是多少。


Sol

这题很妙。

考虑用概率来表示出现次数。

注意到如果一个点的祖先有标记,那么他也有可能被影响。

可以记a为i这一个节点的1的出现概率,b为i和i的祖先中有一个出现出现1的概率。

这两个互相转移一下。

大致思路是把点分成四类:经过的,覆盖的,pushdown的和覆盖的点的儿子。

最后一类需要区间加。

技术图片
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define maxn 100005
#define ll long long
#define mod 998244353
using namespace std;
int n,m;
ll ny=499122177;
struct node{
    ll a,b,sum,bc,ba;
}tr[maxn*8];
void wh(int k){
    tr[k].sum=(tr[k*2].sum+tr[k*2+1].sum+tr[k].a)%mod;
}
void ch1(int k){
    tr[k].a=tr[k].a*ny%mod;tr[k].b=tr[k].b*ny%mod;wh(k);
}
void ch2(int k){
    tr[k].a=ny*(tr[k].a+1)%mod;tr[k].b=ny*(tr[k].b+1)%mod;wh(k);
}
void ch3(int k){
    tr[k].a=(tr[k].b+tr[k].a)*ny%mod;tr[k].a%=mod;wh(k);
}
void ch4(int k){
    tr[k].b=ny*(tr[k].b+1)%mod;
    tr[k].bc=tr[k].bc*ny%mod;tr[k].ba=(tr[k].ba+1)*ny%mod; 
}
void down(int k){
    int ls=k*2,rs=k*2+1; 
    if(tr[k].bc!=1){
        ll &t=tr[k].bc;
        tr[ls].b=tr[ls].b*t%mod;tr[ls].bc=tr[ls].bc*t%mod;tr[ls].ba=tr[ls].ba*t%mod;
        tr[rs].b=tr[rs].b*t%mod;tr[rs].bc=tr[rs].bc*t%mod;tr[rs].ba=tr[rs].ba*t%mod;
        t=1;
    }
    if(tr[k].ba!=0){
        ll &t=tr[k].ba;
        tr[ls].b=(tr[ls].b+t)%mod;tr[ls].ba=(tr[ls].ba+t)%mod;
        tr[rs].b=(tr[rs].b+t)%mod;tr[rs].ba=(tr[rs].ba+t)%mod;
        t=0;
    }
}
void add(int k,int l,int r,int li,int ri){
    if(l>=li&r<=ri){
        ch2(k);
        //if(l<r)ch4(k*2),ch4(k*2+1);
        tr[k].ba=(tr[k].ba+1)*ny%mod;
        tr[k].bc=tr[k].bc*ny%mod;
        return;
    }
    down(k);
    int mid=l+r>>1;
    if(li<=mid)add(k*2,l,mid,li,ri);
    else ch3(k*2);
    if(ri>mid)add(k*2+1,mid+1,r,li,ri);
    else ch3(k*2+1);
    ch1(k);
}
int main(){
    cin>>n>>m;
    ll num=1;
    for(int i=1;i<maxn;i++)tr[i].bc=1;
    for(int i=1,op,l,r;i<=m;i++){
        scanf("%d",&op);
        if(op==1){
            scanf("%d%d",&l,&r);
            add(1,1,n,l,r);
            num=num*2%mod;
        }
        else {
            ll ans=tr[1].sum*num%mod;
            ans=(ans+mod)%mod;
            printf("%lld
",ans);
        }
    }
    return 0;
}
View Code

 

以上是关于线段树的主要内容,如果未能解决你的问题,请参考以下文章

线段树

CCF(除法):线段树区间修改(50分)+线段树点修改(100分)+线段树(100分)

线段树合并

数据结构——线段树

论线段树:二

线段树