天天看點

樹上莫隊算法

簡介

樹上莫隊,顧名思義就是把莫隊搬到樹上。

我們從一道題目入手[SDOI2018]原題識别 SPOJ Count on a tree II

題目意思很明确:給定一個$n$個節點的樹,每個節點表示一個整數,問$u$到$v$的路徑上有多少個不同的整數。

像這種不帶修改數顔色的題首先想到的肯定是樹套樹莫隊,那麼如何把在序列上的莫隊搬到樹上呢?

算法

歐拉序

我們考慮用什麼東西可以把樹上的問題轉化到序列上,dfs序是可以的,但是這道題不行(無法搞lca的貢獻)

有一種神奇的東西,叫做歐拉序。

它的核心思想是:當通路到點$i$時,加入序列,然後通路$i$的子樹,當通路完時,再把$i$加入序列

煮個栗子,下面這棵樹的歐拉序為

$1\ 2\ 3\ 4\ 4\ 5\ 5\ 6\ 6\ 3\ 7\ 7\ 2\ 1$

樹上莫隊算法

樹上莫隊

有了這個有什麼用呢?

我們考慮我們要解決的問題:求$x$到$y$的路徑上有多少個不同的整數

這裡我們設$st[i]$表示通路到$i$時加入歐拉序的時間,$ed[i]$表示回溯經過$i$時加入歐拉序的時間

不妨設$st[x]<st[y]$(也就是先通路$x$,再通路$y$)

分情況讨論 

若$lca(x,y) = x$,這時$x,y$在一條鍊上,那麼$st[x]$到$st[y]$這段區間中,有的點出現了兩次,有的點沒有出現過,這些點都是對答案沒有貢獻的,我們隻需要統計出現過$1$次的點就好

比如當詢問為$2,6$時,$(st[2],st[6])=2\ 3\ 4\ 4\ 5\ 5\ 6$,$4,5$這兩個點都出現了兩次,是以不統計進入答案

若$lca(x,y) \not = x$,此時$x,y$位于不同的子樹内,我們隻需要按照上面的方法統計$ed[x]$到$st[y]$這段區間内的點。 

比如當詢問為$4,7$時,$(ed[4],st[7]) = 4\ 5\ 5\ 6\ 6\ 3\ 7\ $。大家發現了什麼?沒錯!我們沒有統計$lca$,是以我們需要特判$lca$

然後就沒啦,開始愉快的調代碼吧

相關證明

此處純為作者瞎扯。。。

  • 為什麼出現兩次的點不統計答案

樹上路徑的定義為:從$x$到$y$經過節點個數最少的路徑。

若一個點$k$出現兩次,說明我們可以先通路$k$,進入$k$的子樹中,然後出來,再到$y$,很顯然不通路$k$是更優的。是以出現兩次的點不能統計入答案

  • 為什麼當$lca(x,y) \not =x$時需要從$ed[x]$開始周遊

從$st[x]$到$ed[x]$為$x$的子樹中的節點,很顯然這些節點不能統計進答案

代碼

注意我們詢問的區間長度為$2*N$,是以預處理的時候一定要循環到$2*N$!

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
char buf[1 << 21], *p1 = buf, *p2 = buf;
using namespace std;
const int MAXN = 1e5 + 10;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int N, Q;
int belong[MAXN], block;
struct Query {
    int l, r, ID, lca, ans;
    bool operator < (const Query &rhs) const{
        return belong[l] == belong[rhs.l] ? r < rhs.r : belong[l] < belong[rhs.l];
    //    return belong[l] < belong[rhs.l];
    }
}q[MAXN];
vector<int>v[MAXN];
int a[MAXN], date[MAXN];
void Discretization() {
    sort(date + 1, date + N + 1);
    int num = unique(date + 1, date + N + 1) - date - 1;
    for(int i = 1; i <= N; i++) a[i] = lower_bound(date + 1, date + num + 1, a[i]) - date;    
}
int deep[MAXN], top[MAXN], fa[MAXN], siz[MAXN], son[MAXN], st[MAXN], ed[MAXN], pot[MAXN], tot;
void dfs1(int x, int _fa) {
    fa[x] = _fa; siz[x] = 1;
    st[x] = ++ tot; pot[tot] = x; 
    for(int i = 0; i < v[x].size(); i++) {
        int to = v[x][i];
        if(deep[to]) continue;
        deep[to] = deep[x] + 1;
        dfs1(to, x);
        siz[x] += siz[to];
        if(siz[to] > siz[son[x]]) son[x] = to;
    }
    ed[x] = ++tot; pot[tot] = x;
}
void dfs2(int x, int topfa) {
    top[x] = topfa;
    if(!son[x]) return ;
    dfs2(son[x], topfa);
    for(int i = 0; i < v[x].size(); i++) {
        int to = v[x][i];
        if(top[to]) continue;
            dfs2(to, to);
    }
}
int GetLca(int x, int y) {
    while(top[x] != top[y]) {
        if(deep[top[x]] < deep[top[y]]) swap(x, y);
        x = fa[top[x]];
    }
    return deep[x] < deep[y] ? x : y;
}
void DealAsk() {
    for(int i = 1; i <= Q; i++) {
        int x = read(), y = read();
        if(st[x] > st[y]) swap(x, y);
        int _lca = GetLca(x, y);
        q[i].ID = i;
        if(_lca == x) q[i].l = st[x], q[i]. r = st[y];
        else q[i].l = ed[x], q[i].r = st[y], q[i].lca = _lca;
    }
}
int Ans, out[MAXN], used[MAXN], happen[MAXN];
void add(int x) {
    if(++happen[x] == 1) Ans++;
}
void delet(int x) {
    if(--happen[x] == 0) Ans--;
}
void Add(int x) {
    used[x] ? delet(a[x]) : add(a[x]); used[x] ^= 1;
}
void Mo() {
    sort(q + 1, q + Q + 1);
    int l = 1, r = 0, fuck = 0;
    for(int i = 1; i <= Q; i++) {
        while(l < q[i].l) Add(pot[l]), l++, fuck++;
        while(l > q[i].l) l--, Add(pot[l]), fuck++;
        while(r < q[i].r) r++, Add(pot[r]), fuck++;
        while(r > q[i].r) Add(pot[r]), r--, fuck++;
        if(q[i].lca) Add(q[i].lca);
        q[i].ans = Ans;
        if(q[i].lca) Add(q[i].lca);
    }
    for(int i = 1; i <= Q; i++) out[q[i].ID] = q[i].ans;
    for(int i = 1; i <= Q; i++)
        printf("%d\n", out[i]);
}
int main() {
    N = read(); Q = read();
    //block = 1.5 * sqrt(2 * N) + 1;
    //block = pow(N, 0.66666666666);
    block = sqrt(N);
    for(int i = 1; i <= N; i++) a[i] = date[i] = read();
    for(int i = 1; i <= N * 2; i++) belong[i] = i / block + 1;
    Discretization();
    for(int i = 1; i <= N - 1; i++) {
        int x = read(), y = read();
        v[x].push_back(y); v[y].push_back(x);
    }
    deep[1] = 1; dfs1(1, 0);
    dfs2(1, 1);
/*    for(int i = 1; i <= N; i++)    
        for(int j = 1; j <= i - 1; j++)
            printf("%d %d %d\n", i, j, GetLca(i, j));*/
    DealAsk();
    Mo();
    return 0;
}      

作者:自為風月馬前卒

個人部落格http://attack204.com//

出處:http://zwfymqz.cnblogs.com/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。