線段樹模板

其實從寒假就知道線段樹這個東西了,但是嫌線段樹寫得長,一直用樹狀數組。
但最近發現線段樹也很不錯,於是就去做洛谷的兩個線段樹模板題(第一個模板曾經用樹狀數組A過)。
又因爲去機房的時間較短較分散,於是就在自習課上對着線段樹的那張圖imagine線段樹的原理並手動coding。
自己畫的,好醜
對就是這張醜圖
先說三個宏定義

#define ls (i << 1)
#define rs ((i << 1) | 1)
#define mid ((n[i].l + n[i].r) >> 1)

一定要加括號啊!!!
首先我們要建樹
怎麼看都要二分遞歸嘛~~~
遞歸到葉子(l == r)就把初始數組的相應位置的值丟到線段樹裏
然後往上回溯,順便改區間值

inline void built(int i, int l, int r)//過去式防與關鍵字衝突
{
    n[i].l = l;
    n[i].r = r;
    if(l == r)
    {
        n[i].sm = read();
        return ;
    }
    int md = (l + r) >> 1;
    built(ls, l, md);
    built(rs, md + 1, r);
    ud(i);
    return ; 
}

如果輸入順序就是初始數組的順序就不用存初始數組了,直接建樹時讀入(反正先遞歸左邊的區間)
ud就是updata,改區間值,簡單粗暴

inline void ud(int i)
{
    n[i].sm = (n[ls].sm + n[rs].sm) % P;
    return ;
}

然後就是單點修改
從根跑到葉子
跑到哪改到哪

inline void cp(int i, int k, int x)// change point
{
    if(n[i].l == k && k == n[i].r)
    {
        n[i].sm += x;
        return ;
    }
    if(k <= mid) cp(ls, k, x);
    else         cp(rs, k, x);
    return ;
}

區間查詢也很無腦
如果我要查的區間完全蓋住了當前區間,就返回當前的區間值。如果只有一部分重合,就擺~動~(dx:擺動大法好)。和左邊有重合就往左擺,和右邊有重合就往右擺。注意mid是在左區間裏。

inline long long gs(int i, int l, int r)
{
    if(l <= n[i].l && r >= n[i].r)
        return n[i].sm;
    long long ans = 0;
    if(l <= mid) ans += gs(ls, l, r);//區間端點完全傳下去!!!
    if(r > mid)  ans += gs(rs, l, r);
    return ans;
}

區間修改就麻煩些了
如果我們仍然從根開始,跑到哪改到哪,直到每個葉子,其實複雜度比暴力還高
於是我們就想優化:Lazy!
如果當前區間完全要被修改,就只改這個區間而不去改它的兒子們(就是懶得改兒子),我們下次在見到這個區間時(不管是修改時還是查詢時見到它),再去改它的F1兩個兒子
也就是打Lazy標記和把Lazy標記傳給兒子(Push Down)
Lazy標記的意義:當前區間已被修改而它的兒子沒有改

inline void pda(int i, int ln, int rn)
{
    n[ls].lza = (n[ls].lza + n[i].lza) % P;
    n[rs].lza = (n[rs].lza + n[i].lza) % P;
    //父親的lazy給兒子
    n[ls].sm = (n[ls].sm + n[i].lza * ln) % P;
    n[rs].sm = (n[rs].sm + n[i].lza * rn) % P;
    n[i].lza = 0;
    //它的兒子被改了,它自己就不用Lazy了
    return ;
}
inline void csa(int i, int l, int r, int x)
{
    if(n[i].l >= l && n[i].r <= r)
    {
        n[i].sm = (n[i].sm + x * (n[i].r - n[i].l + 1)) % P;
        //區間的sum都被加了x,所以要乘區間長度!!!
        n[i].lza = (n[i].lza + x) % P;
        //lazy標記就只記這個區間要+x,加多少取決於區間長,與lazy標記無關(果然有夠lazy)
        return ;
    }
    if(n[i].lza) pda(i, mid - n[i].l + 1, n[i].r - mid);
    if(mid < r)  csa(rs, l, r, x);
    if(l <= mid) csa(ls, l, r, x);
    ud(i);
    return ;
}

以上內容完全可以用樹狀數組實現嘛QWQ
但如果加法和乘法一塊改區間時樹狀數組就炸了
然而我線段樹也炸了調了1h
我們用加法lazy和乘法lazy共同維護線段樹

  • 如果我先加一個數再乘一個數,由乘法分配律知,相當於先乘一個數再加一個數
    所以傳標記時先傳乘再傳加
  • 區間每個數都乘一個數,由乘法結合律知,相當於整個區間都乘一個數,即a1 * x + a2 * x … = x * (a1 + a2 + …),即區間乘修改時不用乘區間長度
  • lazy乘的初值賦1!!!x * 0 = 0, x * 1 = x
  • 乘法會影響加法,因爲先傳了乘法。所以lazy乘改了lazy加也要相應的改

其實線段樹最簡單的地方就是可以複製粘貼,區間乘把區間加的複製過來一改就行

inline void pdm(int i, int ln, int rn)
{
    n[ls].lzm = (n[ls].lzm * n[i].lzm) % P;
    n[rs].lzm = (n[rs].lzm * n[i].lzm) % P;
    n[ls].lza = (n[ls].lza * n[i].lzm) % P;
    n[rs].lza = (n[rs].lza * n[i].lzm) % P;
    n[ls].sm = (n[ls].sm * n[i].lzm) % P;
    n[rs].sm = (n[rs].sm * n[i].lzm) % P;
    n[i].lzm = 1;
    return ;
}
inline void csm(int i, int l, int r, int x)
{
    if(n[i].l >= l && n[i].r <= r)
    {
        n[i].sm = (n[i].sm * x) % P;
        n[i].lzm = (n[i].lzm * x) % P;
        n[i].lza = (n[i].lza * x) % P;
        return ;
    }
    if(n[i].lzm != 1) pdm(i, mid - n[i].l + 1, n[i].r - mid);
    if(n[i].lza)      pda(i, mid - n[i].l + 1, n[i].r - mid);
    if(mid < r)  csm(rs, l, r, x);
    if(l <= mid) csm(ls, l, r, x);
    ud(i);
    return ;
}

長得好像有木有っ゚Д゚)っ
最後就是完整的模板了
真的好長

/*********
push down multiply first
then push down add
*********/
#include <cstdio>
using namespace std;

inline long long read()
{
    long long n = 0,k = 1;
    char ch = getchar();
    while ((ch > '9' || ch < '0') && ch != '-')  ch = getchar();
    if(ch == '-') k = -1, ch = getchar();
    while (ch <= '9' && ch >= '0')
    {
          n = n * 10 + ch - '0';
          ch = getchar();
    }
    return n * k;
}

inline void print(long long n)
{
    if(n < 0) {putchar('-'); n = -n;}
    if(n > 9) print(n / 10);
    putchar(n % 10 + '0');
    return ;
}

struct Node
{
    int l, r;
    long long sm, lza, lzm; //lazy_multiply
    Node()
    {
        lzm = 1;
    }
}n[500420];
long long N, M, P;

#define ls (i << 1)
#define rs ((i << 1) | 1)
#define mid ((n[i].l + n[i].r) >> 1)
inline void ud(int i)
{
    n[i].sm = (n[ls].sm + n[rs].sm) % P;
    return ;
}

inline void built(int i, int l, int r)
{
    n[i].l = l;
    n[i].r = r;
    if(l == r)
    {
        n[i].sm = read();
        return ;
    }
    int md = (l + r) >> 1;
    built(ls, l, md);
    built(rs, md + 1, r);
    ud(i);
    return ; 
}

inline void pda(int i, int ln, int rn)
{
    n[ls].lza = (n[ls].lza + n[i].lza) % P;
    n[rs].lza = (n[rs].lza + n[i].lza) % P;
    n[ls].sm = (n[ls].sm + n[i].lza * ln) % P;
    n[rs].sm = (n[rs].sm + n[i].lza * rn) % P;
    n[i].lza = 0;
    return ;
}

inline void pdm(int i, int ln, int rn)
{
    n[ls].lzm = (n[ls].lzm * n[i].lzm) % P;
    n[rs].lzm = (n[rs].lzm * n[i].lzm) % P;
    n[ls].lza = (n[ls].lza * n[i].lzm) % P;
    n[rs].lza = (n[rs].lza * n[i].lzm) % P;
    n[ls].sm = (n[ls].sm * n[i].lzm) % P;
    n[rs].sm = (n[rs].sm * n[i].lzm) % P;
    n[i].lzm = 1;
    return ;
}

inline long long as(int i, int l, int r)  // answer section
{
    if(n[i].l >= l && n[i].r <= r)
        return n[i].sm;
    if(n[i].lzm != 1) pdm(i, mid - n[i].l + 1, n[i].r - mid);
    if(n[i].lza)      pda(i, mid - n[i].l + 1, n[i].r - mid);
    long long ans = 0;
    if(l <= mid) ans = (ans + as(ls, l, r)) % P;
    if(r > mid)  ans = (ans + as(rs, l, r)) % P;
    return ans;
}

inline void csa(int i, int l, int r, int x)
{
    if(n[i].l >= l && n[i].r <= r)
    {
        n[i].sm = (n[i].sm + x * (n[i].r - n[i].l + 1)) % P;
        n[i].lza = (n[i].lza + x) % P;
        return ;
    }
    if(n[i].lzm != 1) pdm(i, mid - n[i].l + 1, n[i].r - mid);
    if(n[i].lza)      pda(i, mid - n[i].l + 1, n[i].r - mid);
    if(mid < r)  csa(rs, l, r, x);
    if(l <= mid) csa(ls, l, r, x);
    ud(i);
    return ;
}

inline void csm(int i, int l, int r, int x)
{
    if(n[i].l >= l && n[i].r <= r)
    {
        n[i].sm = (n[i].sm * x) % P;
        n[i].lzm = (n[i].lzm * x) % P;
        n[i].lza = (n[i].lza * x) % P;
        return ;
    }
    if(n[i].lzm != 1) pdm(i, mid - n[i].l + 1, n[i].r - mid);
    if(n[i].lza)      pda(i, mid - n[i].l + 1, n[i].r - mid);
    if(mid < r)  csm(rs, l, r, x);
    if(l <= mid) csm(ls, l, r, x);
    ud(i);
    return ;
}

inline void prt()
{
    putchar('#');
    for(register int i = 1; i <= N; i++)
        printf("%lld ", as(1, i, i));
    putchar(10);
    return ;
}

int main()
{
    N = read();
    M = read();
    P = read();
    built(1, 1, N);
    //prt();
    register int f, x, y, z;
    for(register int i = 1; i <= M; i++)
    {
        f = read();
        if(f == 1)
        {
            x = read();
            y = read();
            z = read();
            csm(1, x, y, z);
            //prt();
        }
        else if(f == 2)
        {
            x = read();
            y = read();
            z = read();
            csa(1, x, y, z);
            //prt();
        }
        else
        {
            x = read();
            y = read();
            print(as(1, x, y));
            putchar(10);
            //prt();
        }
    }
    return 0;
}

結合樹剖食用更佳(~ ̄▽ ̄)~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章