thr 樹鏈剖分 dp

2022-03-07 06:18:13 字數 2694 閱讀 7811

首先,可以有乙個$dp$的思路

不難發現本題中,三個點如果互相距離相同,那麼一定有乙個「中心點」到三個點的距離都相同

那麼我們可以把本題轉化計算以每個點為根的情況下,從三個不同的子樹中選擇深度相同的三個點的方案數

進一步,我們選定1號點為根,這樣定義我們的$dp$方程:

$f[u][dis]$表示以$u$為根的子樹中,和$u$的距離為$dis$的點的數量

$g[u][dis]$表示以$u$為根的子樹中已經選擇了不屬於同乙個$u$的兒子子樹的兩個點,並且距離為dis的方案數

那麼轉移方程如下:

$f[u][i]=\sum_f[v][i-1]$

$g[u][i]=\sum_g[v][i+1]+\sum_\sum_ f[p][i-1]*f[q][i-1]$

這個過程非常方便$dp$,但是時間複雜度和空間複雜度都是$o(n^2)$的

然後我們驚喜地發現一件事情:

這裡的$f,g$都可以先繼承乙個兒子的內容,然後再和別的兒子合併!

考慮我們繼承的這個過程,也就是$u$從確定的繼承者$v$那裡獲得資訊的過程:

$f[u][i]=f[v][i-1]$

$g[u][i]=g[v][i+1]$

我們又驚喜地發現,如果我們一整條鏈都用這樣的繼承方式的話,那麼長度為$n$的鏈只需要$o(n)$的空間!

具體而言,我們對這$n$個點開兩個陣列,$f,g$

此時,最下層的葉子最深,它的$f,g$陣列的第二位只有0

那麼$f[leaf][0]=f[1],g[leaf][0]=g[n]$

然後$f[fa[leaf]][0]=f[2],f[fa[leaf]][1]=f[1]$,$g$也類似

就這樣一直下去

可以看到實際上$f[leaf]$儲存了$n$個$dp$值,因為這$n$個是相等的

讓後我們又又驚喜地發現,現在我們的$dp$,可以逐個子樹做合併,每一次合併的複雜度是$maxdep(v)$

這樣我們就得到了乙個時間複雜度$o(n)$,空間複雜度$o(n)$的演算法

ps.本題中其實什麼剖分方法都是一樣的qwq,都是$o(n)$

個人認為這道題是一道好題

其實,原本的$dp$思路並不難想,同時後面的做優化的原因其實都已經很好地植入在上一層的方法裡面了

也就是說,這道題如果你的思維開闊,是很容易順著思路一路就從$o(n2)+o(n2)$優化到$o(n)+o(n)$的

本題中體現的主要思想,在於觀察已有方法中的一些性質,並代入一些非常規手法(啟發式合併和乙個位置儲存多個$dp$值)

這和之前我曾經遇到的一些題目不同

大部分題目是在舊方法裡面找時間複雜度高的部分,然後以這些部分為主要攻關點來進行突破、優化

然而當我們遇到影響複雜度的地方並不集中的演算法時,我們就需要開啟思路,從演算法本身性質入手,套用更優秀的方法來解題

這道題並不是一道「套路題」,不是只要做過類似的東西就能一下子秒掉的

現在的oi考場上,我們遇到的這類題目也一定會越來越多——這實際上意味著oi比賽題目質量的整體上公升

因此我們一定要鍛鍊自己開啟思路、廣撒網的能力,不要老是一條路走到黑(就像我以前想題一樣)

#include#include#include#include#define ll long long

#define fpos deep_dark_fantasy_1

#define gpos deep_dark_fantasy_2

using namespace std;

inline int read()

while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();

return re*flag;

}int n,first[50010],cnte;

struct edgea[100010];

inline void add(int u,int v);first[u]=cnte;

a[++cnte]=(edge);first[v]=cnte;

}int dep[50010],maxdep[50010],fpos[50010],gpos[50010],cntf,cntg,son[50010],siz[50010],top[50010];

ll f[200010],g[200010];

int getf(int u,int i)

int getg(int u,int i)

int getlen(int u)

int fa[50010];

void dfs1(int u,int f)

}void dfs2(int u,int t)

if(!son[u]) return;

dfs2(son[u],t);

for(i=first[u];~i;i=a[i].next)

}ll ans=0;

void dfs(int u)

int i,v,j,lim;

for(i=first[u];~i;i=a[i].next)

}void init()

int main()

dfs1(1,0);dfs2(1,1);

dfs(1);

printf("%lld\n",ans);}}

樹鏈剖分 樹鏈剖分講解

好了,這樣我們就成功解決了對樹上修改查詢邊權或點的問題。下面放上 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 個操作,操作分為如下五種型別 換根 將乙個指定的節點設定為樹的新根。修改路徑權值 給定兩個節點,將這兩個節點間路徑上的所有節點權值 含這兩個節點 增加乙個給定的值。修改子樹權值 給定乙...