1. 程式人生 > >[填坑]樹上差分 例題:[JLOI2014]松鼠的新家(LCA)

[填坑]樹上差分 例題:[JLOI2014]松鼠的新家(LCA)

sca esp name tmp mes font efi 同學 節點

今天算是把LCA這個坑填上了一點點,又復習(其實是預習)了一下樹上差分。其實普通的差分我還是會的,樹上的嘛,也是懂原理的就是沒怎麽打過。

我們先來把樹上差分能做到的看一下:

1.找所有路徑公共覆蓋的邊

例題:[NOIP2015]運輸計劃 (然而我還沒過就先不講了)

反正就是中間有一步要求一條邊被所有計劃公共覆蓋。

那麽怎麽求它呢?暴力(滾粗)。我們有一個非常好的方法就是樹上差分(記錄tmp為差分數組)

詢問操作為從葉子節點的權值向上累加到root

在一條路徑u→ v,如果tmp[u]++,那麽我們往上推的時候相當於u到root所有路徑都被訪問一次。同理tmp[v]++也意味如此。但是,lca(u,v)到root的路徑都沒有被訪問過,但這裏都被標記過兩次,所以我們還要做的操作就是tmp[lca(u,v)]-=2;這樣的話累加完之後tmp[i]記錄的就是i節點被多少條路徑覆蓋了。

2.將路徑上的所有點權值+1,最後求點權

例題:[JLOI2014]松鼠的新家 (這個我做過了hhh)

題目大意就是給你一些路徑,把這個路徑經過的點權+1,最後求所有點權。

這個題今天卡了我了。同學大佬有拿樹剖求的,而且還要差分。但是我對於樹上差分有點蒙蔽,於是搜了搜。然而蒟蒻的我搜到了LCA解法,於是興高采烈的打(chao)了(le)出來。這裏的差分有一些不同。因為我們要找的是點的覆蓋。所以我們對於u→ v,tmp[u]++,tmp[v]++,tmp[lca(u,v)]--,tmp[fa[lca(u,v)]]--;這個想必大家能看懂吧。

於是,我們就歡快的求出了所有點被修改後的權值。

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#define pos(i,a,b) for(int i=(a);i<=(b);i++)
using namespace std;
#define N 301000
struct haha{
    int next,to;
}edge[N*2];
int head[N],cnt=1,p[N][20];
void add(int u,int v){
    edge[cnt].to=v;edge[cnt].next=head[u];head[u]=cnt++;
}
int a[N],n,fa[N],dep[N];
void dfs(int x){
    for(int i=head[x];i;i=edge[i].next){
        int to=edge[i].to;
        if(fa[x]!=to){
            fa[to]=x;dep[to]=dep[x]+1;
            dfs(to);
        }
    }
}
void init(){
    int j;
    for(j=0;(1<<j)<=n;j++){
        pos(i,1,n) p[i][j]=-1;
    }
    pos(i,1,n){
        p[i][0]=fa[i];
    }
    for(j=1;(1<<j)<=n;j++){
        pos(i,1,n){
            if(p[i][j-1]!=-1){
                p[i][j]=p[p[i][j-1]][j-1];
            }
        }
    }
}
int lca(int a,int b){
    int i;
    if(dep[a]<dep[b]) swap(a,b);
    for(i=0;(1<<i)<=dep[a];i++);
    i--;
    for(int j=i;j>=0;j--)
        if(dep[a]-(1<<j)>=dep[b])
            a=p[a][j];
    if(a==b) return a;
    for(int j=i;j>=0;j--){
        if(p[a][j]!=-1&&p[a][j]!=p[b][j]){
            a=p[a][j];b=p[b][j];
        }
    }
    return fa[a];
}
int tmp[N];
void work(int x){
    for(int i=head[x];i;i=edge[i].next){
        int to=edge[i].to;
        if(fa[x]!=to){
            work(to);
            tmp[x]+=tmp[to];
        }
    }
}
int main(){
    scanf("%d",&n);
    pos(i,1,n)
        scanf("%d",&a[i]);
    pos(i,1,n-1){
        int x,y;scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs(a[1]);
    init();
    pos(i,1,n-1){
        int u=a[i],v=a[i+1];
        tmp[u]++;tmp[v]++;
        tmp[lca(u,v)]--;tmp[fa[lca(u,v)]]--;
    }
    work(a[1]);
    pos(i,2,n) tmp[a[i]]--;
    pos(i,1,n) printf("%d\n",tmp[i]);
    return 0;
} 

  

[填坑]樹上差分 例題:[JLOI2014]松鼠的新家(LCA)