zkw线段树(详解)

定义

我们已经了解了线段树的许多操作与结构

但是有一些缺点:

1.它是递归操作的

所以空间可能会很大

2.叶子节点深度都不同

这样操作会很麻烦

3.代码量太大...

所以现在可以引进zkw线段树

和普通的线段树相比,zkw线段树主要有这样几个不同点:

1.所有的叶子结点都在同一深度

2.左右两端各增加了一个哨兵

3.每个点可以根据自己的编号计算父节点编号

以上三点保证了zkw线段树可以自底向上非递归

做法:

怎么做呢?

我们现在要在最底层设置n个点,同时还要加上2个卫兵(作用后面会有),所以最底层至少n+2个点

根据完全二叉树的性质(第i层会有2^{i}个点),我们可以把最底层的第一个标号求出来

注意:这里最底层不一定刚好要存下,但是最底层至少要有这么多的点

接着,就是预处理了

void pre(){
    m = 1;
    while( m <= n + 1 ) m *= 2;//找最底层的第一个点
    for( int i = n + m + 1 ; i >= 1; i -- ){
        if( i >= m )
            tre[i] = a[i-m];//如果这个点是叶子,那么它存的就是初始值
        else
            tre[i] = tre[i*2] + tre[i*2+1];//否则是它的左儿子+右儿子

    }
}

单点修改 

很简单,把这个叶子修改后,依次 / 2,一个个更新他的祖先 O(logn)

void add( int x , int delta ){
    x += m;
    while( x ){
        tre[x] += delta;
        x /= 2;
    }

区间查询

这个时候我们就需要用到卫兵了

我们已知区间左右端点L , R,那我们得到它们卫兵的两个位置是L-1,R+1

现在把卫兵往上移

如果我们发现当前卫兵是它父亲的左端点,那么它父亲的右端点就会是在这个区间范围内(自己推)

那么把它标兵的兄弟就加进我们的ans中来

void query( int x , int y ){
    int s = x + m - 1 , l = y + m + 1 , ans = -1;//ans存的是区间最大值
    while( s || l ){
        if( s / 2 != l / 2 ){//如果两个点两个标兵已经跳到统一个点了,那么整个区间就找完了
            if( s % 2 == 0 )
                ans = max( tre[s+1] , ans );
            if( l % 2 == 1 )
                ans = max( tre[l-1] , ans );
        }
        s /= 2 , l /= 2;
    }
    printf( "%d\n" , ans );
}

现在有一道题:LCIS

这道题就是用的了区间修改和单点查询

但是不同的是,如果我们直接找肯定很难做到

所以我们在每一个节点(tre[]数组)中多加几个情况

每一个tre[i]里多保存:

1.以它为子树的叶子节点个数

2.从它左端点开始为起点得到的LCIS

2.从它右端点开始为起点得到的LCIS

4.整个区间的最大LCIS

由于自己只会用笨方法,所以我把每一个LCIS起点下标,终点下标,长度都用了另一个结构体存了起来

那么区间合并就可以这样子做了:

左端点的LCIS就是两种情况:

1.如果它的左儿子的左端点LCIS包含整个区间

      则就是左端点区间的大小加上右儿子的左端点LCIS

否则

    就是左儿子的左端点的LCIS

更新右端点的LCIS同理

那么整个区间最大的LCIS就三个情况

左儿子的区间最大LCIS

右儿子的区间最大LCIS

如果左儿子的右端点的值比右儿子的左端点的值还小

那么就还有一个情况:左儿子右端点开始的LCIS + 右儿子左端点开始的LCIS

而这些所有的东西,都可以用重载运算符做:

struct edge{
    int l , r;
    int cd;
};
struct node{
    edge sl , sr , sz;//sl是左端点的LCIS,sz是整个区间的LCIS,sr是右端点的LCIS
    int len;
    void qk(){
        len = sl.cd = sl.l = sl.r = 0;
        sr = sz = sl;
    }
    friend node operator + ( node i , node j ){
        node ans;
        ans.qk();
        //memset( ans , 0 , sizeof( ans ) );
        ans.len = i.len + j.len;
        if( i.sl.cd == i.len && a[i.sl.r] < a[j.sl.l] ){
            ans.sl.cd = i.len + j.sl.cd;
            ans.sl.l = i.sl.l;
            ans.sl.r = j.sl.r;
        }
        else
            ans.sl = i.sl;
        if( j.sr.cd == j.len && a[i.sr.r] < a[j.sr.l] ){
            ans.sr.cd = j.len + i.sr.cd;
            ans.sr.l = i.sr.l;
            ans.sr.r = j.sr.r;
        }
        else
            ans.sr = j.sr;
        if( i.sz.cd >= j.sz.cd )
            ans.sz = i.sz;
        else
            ans.sz = j.sz;
        if( a[i.sr.r] < a[j.sl.l] && i.sr.r + 1 == j.sl.l && ans.sz.cd < i.sr.cd + j.sl.cd ){
            ans.sz.cd = i.sr.cd + j.sl.cd;
            ans.sz.l = i.sr.l , ans.sz.r = j.sl.l;
        }
        return ans;
    }
}tre[MAXN*4];

区间修改&&区间查询

同样用类似懒标记的方法

只不过这样是永久标记化简单的说就是以和区间查询相同的方式,将区间表示为多个子树相加,在每个受到影响的子树上打上标记。查询的时候如果访问到某个有标记的子树,就将这个标记对所查询区间的影响反映到答案中

void add( int x , int y , ll delta ){
    int lsum = 0 , rsum = 0;
    x = x + m - 1;
    y = y + m + 1;
    while( x || y ){
        tre[x] += lsum * delta;
        tre[y] += rsum * delta;
        if( x / 2 != y / 2 ){
            if( x % 2 == 0 ){
                tre[x+1] += delta * len[x+1];
                flag[x+1] += delta;
                lsum += len[x+1];
            }
            if( y % 2 == 1 ){
                tre[y-1] += delta * len[y-1];
                flag[y-1] += delta;
                rsum += len[y-1];
            }
        }
        x /= 2;y /= 2;
    }
}
void query( int x , int y ){
    int lsum = 0 , rsum = 0;
    x = x + m - 1;
    y = y + m + 1;
    ll ans = 0;
    while( x || y ){
        ans += lsum * flag[x];
        ans += rsum * flag[y];
        if( x / 2 != y / 2 ){
            if( x % 2 == 0 ){
                ans += tre[x+1];
                lsum += len[x+1];
            }
            if( y % 2 == 1 ){
                ans += tre[y-1];
                rsum += len[y-1];
            }
        }
        x /= 2;y /= 2;
    }
    printf( "%lld\n" , ans );
}
发布了68 篇原创文章 · 获赞 7 · 访问量 3859

猜你喜欢

转载自blog.csdn.net/weixin_43823476/article/details/92833064