尤拉序 差分代替樹鏈剖分維護樹上資訊

2021-10-04 16:05:26 字數 3283 閱讀 6742

如果用樹鏈剖分來寫,這當然是一道簡單的維護問題,但是今天的主題是尤拉序(莫名給題加難度了)。

先看一張圖,我們列寫一下本題的尤拉序的列寫方法,這道題的尤拉序的跑法是進出各只記錄一次。

對應尤拉序:a-b-d-d-e-e-f-f-b-c-c-a

所以說,尤拉序是很活的,對應不同的問題,有時候需要用不一樣的方法來跑。

那麼,我們也很容易的確定每個點的進尤拉序出尤拉序。這個很有用。

現在,就拿剛才這張圖舉例,如果我們用差分的思想,給進點「+1」,給出點「-1」,那麼,譬如說,我現在要修改「b--e」這段枝上的點,那麼實際上,可以集體的給「b-d-d-e」和「e-f-f-b」這兩段區間都「+val」,「val」是要增加的值。因為不在這條鏈上的點都會根據「進一出一」這樣的差分原則,最終抵消掉了,因為我們查詢的方式,所以最終只會留下前段的值,而減去後段的值是為了查詢更大子樹的時候不影響外面的答案。

剛才的這段話在**上的表述:

update(1, 1, _up, l[x], l[y], val); //update x to y(x if father of y)

update(1, 1, _up, r[y], r[x], val);

這樣基礎的操作有了之後,我們仍然未說明查詢的操作。

譬如說,我們現在要查詢某一條鏈上的權值和了。

那麼,我們實際上只看他們的進尤拉序就可以了,因為出尤拉序是減去的部分,我們只留下要的部分,不要的部分不管,**上是這樣表述:

query(1, 1, _up, l[x], l[y]); //x if father of y
這麼寫是正確的原因還是在於非這條鏈但是卻在尤拉序**現的點,他們的出尤拉點也一定在查詢區間上。這也是差分的神奇之處。

對了,注釋中的father都是指的祖先,我們用深度判一下就可以區別的了。

那麼,問題來了,如果兩個點不在由根結點下來的一條鏈上,而是在兩條鏈上,中間由lca結點分割呢?這都提到lca了,自然求乙個lca,分兩段不就好了嘛?

lca怎麼求?rmq維護一下(碼量++)。

還有個問題,對子樹全體更新呢?

因為查詢的鏈是查到它對應的「進尤拉序」,所以我們對x的子樹全體都增加a的話,實際上,可以看成:

update(1, 1, _up, l[x], r[x], a);   //update x is root
給出例題的**:

#include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#include #define lowbit(x) ( x&(-x) )

#define pi 3.141592653589793

#define e 2.718281828459045

#define inf 0x3f3f3f3f

#define eps 1e-8

#define half (l + r)>>1

#define lsn rt<<1

#define rsn rt<<1|1

#define lson lsn, l, mid

#define rson rsn, mid+1, r

#define ql lson, ql, qr

#define qr rson, ql, qr

#define myself rt, l, r

#define mp(a, b) make_pair(a, b)

using namespace std;

typedef unsigned long long ull;

typedef unsigned int uit;

typedef long long ll;

const int maxn = 2e5 + 7;

int n, q, head[maxn], cnt, _up;

struct eddge

}edge[maxn << 1];

inline void addeddge(int u, int v)

inline void _add(int u, int v)

ll a[maxn], work[maxn], sum[maxn];

int l[maxn], r[maxn], tot;

void euler_dfs(int u, int fa)

r[u] = ++tot; work[tot] = a[u]; sum[tot] = -1;

}ll lazy[maxn << 2], tree[maxn << 2];

inline void pushup(int rt)

inline void pushdown(int rt, int l, int r)

}void build(int rt, int l, int r)

int mid = half;

build(lson); build(rson);

pushup(rt);

}void update(int rt, int l, int r, int ql, int qr, ll val)

pushdown(myself);

int mid = half;

if(qr <= mid) update(ql, val);

else if(ql > mid) update(qr, val);

else

pushup(rt);

}ll query(int rt, int l, int r, int ql, int qr)

inline void init()

int main()

euler_dfs(1, 0);

_up = n << 1;

for(int i=1; i<=_up; i++) sum[i] += sum[i - 1];

build(1, 1, _up);

int op, x, a;

while(q--)

else if(op == 2)

else

}return 0;}/*

5 51 2 3 4 5

1 21 4

2 32 5

3 31 2 1

3 52 1 2

3 3*/

樹鏈剖分 樹鏈剖分講解

好了,這樣我們就成功解決了對樹上修改查詢邊權或點的問題。下面放上 vector v maxn int size maxn dep maxn val maxn id maxn hson maxn top maxn fa maxn 定義 int edge 1,num 1 struct tree e ma...

演算法入門 樹鏈剖分 輕重鏈剖分

目錄 3.0 求 lca 4.0 利用資料結構維護資訊 5.0 例題 參考資料 資料結構入門 線段樹 發表於 2019 11 28 20 39 dfkuaid 摘要 線段樹的基本 建樹 區間查詢 單點修改 及高階操作 區間修改 單點查詢 區間修改 區間查詢 標記下傳 標記永久化 閱讀全文 樹鏈剖分用...

樹鏈剖分 樹剖換根

這是一道模板題。給定一棵 n 個節點的樹,初始時該樹的根為 1 號節點,每個節點有乙個給定的權值。下面依次進行 m 個操作,操作分為如下五種型別 換根 將乙個指定的節點設定為樹的新根。修改路徑權值 給定兩個節點,將這兩個節點間路徑上的所有節點權值 含這兩個節點 增加乙個給定的值。修改子樹權值 給定乙...