長鏈剖分優化樹上dp

2021-10-05 10:18:40 字數 1741 閱讀 8604

長鏈剖分可以把維護子樹中只與深度有關的資訊做到線性的時間複雜度。

例題cf1009f

給一棵樹,定義每個點的dom值為,以該點為根的子樹重中,節點數最多的那一層的層數,即那一層距離這個根節點有幾條邊,如果多層節點數相同,取最小的層數。例如單獨葉節點的dom值是0,一條垂直的鏈的dom值也是0(每層數量都是1,取最前面的)

定義dp[i][j]為i樹第j層的節點數,如果暴力遍歷複雜度是n^2。我們用類似啟發式合併的思想,對於長鏈(最深而非最重的鏈)處理後的結果直接拿給父節點用,然後再去依次解決輕節點,並把這些輕節點向父節點暴力合併。由於所有的輕鏈合併到深鏈上之後就「失效」了,之後再也用不到,所有整體時間複雜度只有o(n)。(太nb了這個演算法……

由於剖分的性質,一條鏈上的節點在乙個區間,子樹的節點也都在乙個區間,所以dp陣列不必為難開n^2大小,我們考慮用指標實現,這樣也實現了重兒子o(1)傳遞dp陣列給父親,實際上就是用了同一塊記憶體,重兒子跑完了直接父親就順著用(看**吧。

123

4567

891011

1213

1415

1617

1819

2021

2223

2425

2627

2829

3031

3233

3435

3637

3839

4041

4243

4445

4647

4849

5051

5253

5455

5657

5859

6061

6263

6465

6667

6869

7071

7273

7475

7677

7879

8081

82

#includeusing namespace std;

const int maxn=1e6+4;

struct edgees[maxn*2];

int head[maxn],cnt;

void add(int u,int v);

head[u]=cnt;

}int h[maxn],son[maxn];

void dfs1(int x,int fa)

}int *dp[maxn],g[maxn],dfn[maxn]; //dp[i][j]表示和根節點i相距j條邊的子孫數量(樹的第j層節點個數)

void dfs2(int x,int fa)

}int ans[maxn];

void solve_dp(int x,int fa)

dp[x][0]=1;

if(dp[x][ans[x]]==dp[x][0]) //如果x只有乙個子節點,且是一條垂直的鏈,那麼它的ans可以再向前提

ans[x]=0;

for(int i=head[x];i;i=es[i].next)

}}int n;

int main()

dfs1(1,0);

dfs2(1,0);

solve_dp(1,0);

for(int i=1;i<=n;i++)

printf("%d\n",ans[i]);

return 0;

}

長鏈剖分優化樹形dp

apio鐵牌告辭 開場想打暴力然後gedit碼 5個小時沒寫完三題最低檔暴力真是快樂 聽課也就學到了一丟丟這個東西。模板題 首先k級兄弟可以一遍dfs的時候丟到k級父親上變成求k級孩子的詢問。求k級孩子有個很簡單的做法,直接dfs的時候維護掃到某個點時記錄下的每種dep出現次數,對於所有求這個點k級...

長鏈剖分隨想

之前寫了那麼長一篇blog 現在不如寫篇小短文 說一下另一種樹鏈剖分方法 長鏈剖分的事情。它可以比重鏈剖分更快地完成一些東西。樹鏈剖分的原始版本重鏈剖分非常經典,這裡就不從頭介紹了。原本的剖分方法是按照子樹大小剖分,與子樹點數最多的兒子連成鏈,所以叫做重鏈剖分 然後顯然就有乙個點到根的路徑上至多 o...

長鏈剖分隨想

之前寫了那麼長一篇blog 現在不如寫篇小短文 說一下另一種樹鏈剖分方法 長鏈剖分的事情。它可以比重鏈剖分更快地完成一些東西。樹鏈剖分的原始版本重鏈剖分非常經典,這裡就不從頭介紹了。原本的剖分方法是按照子樹大小剖分,與子樹點數最多的兒子連成鏈,所以叫做重鏈剖分 然後顯然就有乙個點到根的路徑上至多 o...