1. 程式人生 > >樹鏈剖分( 洛谷P3384 )

樹鏈剖分( 洛谷P3384 )

最大 com 比較 gpo 題目 -- utc tdi r+

我們有時候遇到這樣一類題目,讓我們維護樹上路徑的某些信息,這個時候發現我們無法用線段樹或者樹狀數組來維護這些信息,那麽我們就有著一種新的數據結構,樹剖:將一棵樹劃分成若幹條鏈,用數據結構去維護每條鏈,復雜度為O(logN)。

剖分方法:
盲目剖分
隨機剖分
啟發式剖分
綜合比較,啟發式剖分是剖分時的最佳選擇。

將樹中的邊分為:輕邊和重邊
定義size(X)為以X為根的子樹的節點個數。
令V為U的兒子節點中size值最大的節點,那麽邊(U,V)被稱為重邊,樹中重邊之外的邊被稱為輕邊。

輕重邊路徑剖分的性質

輕邊(U,V),size(V)<=size(U)/2。


從根到某一點的路徑上,不超過O(logN)條輕邊,不超過O(logN)條重路徑。

重鏈剖分

重鏈剖分的過程為2次DFS
第一次:找重邊
第二次:連重邊成重鏈

  1. 找重邊 一次DFS,可記下所有的重邊。
  2. 連重邊成重鏈,以根節點為起點,沿重邊向下拓展,拉成重鏈。不在當前重鏈上的節點,都以該節點為起點向下重新拉一條重鏈。

剖分完之後,每條重鏈就相當於一段區間,用數據結構去維護。

把所有的重鏈首尾相接,放到同一個數據結構上,然後維護這一個整體即可。

修改操作
單獨修改一個點的權值

根據新的編號直接在數據結構中修改就行了。

修改操作 整體修改點 U和點V的路徑上的權值

  1. 如果U和V在同一條重鏈上 : 直接用數據結構修改tid[U]至tid[V]間的值。
  2. 如果U和V不在同一條重鏈上一邊進行修改,一邊將U和V往同一條重鏈上靠,然後就變成了I的情況。

怎樣把他們向一起靠?

A.若fa[top[U]]與V在同一條重鏈上。

修改點U與top[u]間的各權值,然後U跳至fa[top[u],就變成了I的情況。

B.若U向上經過若幹條重鏈和輕邊與V在同一條重鏈上。

不斷地修改當前U和top[u]間的各權值,再將U跳至fa[top[U]],直到U與V在同一條重鏈。

C.若U和V都是向上經過若幹條重鏈和輕邊,到達同一條重鏈。

每次在點U和點V中,選擇dep[top[x]]較大的點x,修改x與top[x]間的各權值,再跳至fa[top[x]],直到點U和點V在同一條重鏈。

情況A、B是情況C的比較特殊的2種。

I也只是II的特殊情況。
所以,這一操作只要用一個過程。

下附代碼:

#include<bits/stdc++.h>
#define sight(c) (‘0‘<=c&&c<=‘9‘)
#define LL int
#define gc nc
#define L(x) (x&-x)
#define eho(x) for(int i=head[x];i;i=net[i])
#define N 200007
LL q1[N],q2[N],gg,sum[N],a[N],T,G;
int tot,fall[N<<1],net[N<<1],head[N],top[N],son[N],f[N],dp[N],siz[N],mo,be[N],ed[N],ok
,n,m,A,B,t[N],op,x,y,z,dla,OS; 
inline char nc(){
    static char buf[1000000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline void swap(int &x,int &y) {x^=y; y^=x; x^=y;}
inline void read(LL &x){
    static char c;
    for(c=gc();!sight(c);c=gc());
    for(x=0;sight(c);c=gc()) x=x*10+c-48;
}
void write(LL x){if (x<10) {putchar(0+x);return;}write(x/10); putchar(0+x%10);}
inline void ADd(int x,int y) {
    fall[++tot]=y; net[tot]=head[x]; head[x]=tot;
}
inline void add(LL &x,LL y) {
    x=x+y; if (x>=mo) x=x%mo; if (x<0) x=x%mo+mo;
} 
inline void Add(LL *A,int x,int dla) {for (;x<N;x+=L(x)) add(A[x],dla);}
inline void adds(int l,int r,int x) {
    Add(q1,l,x); Add(q1,r+1,-x); Add(q2,l,l*x%mo); Add(q2,r+1,-(r+1)*x%mo);
}
inline LL Query(LL *A,int x){for(G=0;x;x-=L(x)) add(G,A[x]);return G;}
inline LL qurey(int l){
    return (sum[l]+(l+1)*1ll*Query(q1,l)-Query(q2,l))%mo;
}
void dfs(int x,int fa){
    siz[x]=1; son[x]=-1; dp[x]=dp[fa]+1; f[x]=fa;
    eho(x) if (fall[i]^fa) {
        dfs(fall[i],x); siz[x]+=siz[fall[i]];
        if ((!(~son[x]))||siz[fall[i]]>siz[son[x]]) son[x]=fall[i]; 
    }
}
void dfs2(int x,int las){
    t[++ok]=x;top[x]=las; be[x]=ok; 
    if (~son[x]) dfs2(son[x],las);
    eho(x) if ((fall[i]^f[x])&&(fall[i]^son[x])) dfs2(fall[i],fall[i]); ed[x]=ok;
}
void apd(int x,int y,LL dla){
    while (top[x]!=top[y]) {
        if (dp[top[x]]<dp[top[y]]) swap(x,y);
        adds(be[top[x]],be[x],dla);
        x=f[top[x]];
    } if (dp[x]>dp[y]) swap(x,y);
    adds(be[x],be[y],dla);
}
LL query_path(int x,int y) {
    LL O=0;
    while (top[x]!=top[y]) {
        if (dp[top[x]]<dp[top[y]]) swap(x,y);
        add(O,qurey(be[x])-qurey(be[top[x]]-1));
        x=f[top[x]];
    } if (dp[x]>dp[y]) swap(x,y);
    add(O,qurey(be[y])-qurey(be[x]-1));
    return O;
}
int main () {
    read(n); read(m); read(OS);read(mo);
    for (int i=1;i<=n;i++) read(a[i]);
    for (int i=1;i< n;i++) {read(A); read(B); ADd(A,B); ADd(B,A); }
    dfs(OS,0); dfs2(OS,OS);
    for (int i=1;i<=n;i++) sum[i]=sum[i-1],add(sum[i],a[t[i]]);
    while (m--) {
        read(op); 
        switch (op) {
            case 1: read(x),read(y),read(dla); apd(x,y,dla); break;
            case 2: read(x),read(y); write(query_path(x,y));putchar(\n); break;
            case 3: read(x); read(z); adds(be[x],ed[x],z);  break;
            case 4: read(x); T=qurey(ed[x])-qurey(be[x]-1); add(T,0ll); 
             write(T); putchar(\n);break;
        }
    } return 0;
}

我用了樹狀數組維護區間,因為這樣比較方便。

不懂的同學點這裏:傳送門

樹鏈剖分( 洛谷P3384 )