樹形dp之換根法
週二週三真的太難了,有早課導致不能熬夜,於是就只能趁着中午的時間寫一寫,這幾天先寫點簡單的東西,就當重新複習了,應該算是給初學者的知識普及,其他的過了週三再說。
首先來講一下樹的重心。
樹的重心,即 樹上到所有點的距離之和最小/以此爲根深度最小/最大子樹大小最小 的點,具有很多方便的性質,如:
1.當一棵樹添加/刪除一個節點,樹的重心最多移動一個位置。(動態維護)(19icpc徐州M題,so~no~chi~no~sa~da~me~)
2.當兩棵樹通過某點連接在一起形成新樹時,新樹的重心一定在連接兩棵舊樹重心的路徑上。(兩樹合併)
3.以一顆樹的重心爲根,劃分的子樹大小一定不超過原樹的一半。(樹分治)
衆所周知,回字有四種寫法,門前有兩棵棗樹,重心也有兩種求法(通常情況下),一種是兩遍dfs找樹上最長路徑的中點,一種就是今天要講的內容——樹形dp之換根法。
首先,初始是不知道重心所在的,最直接的方法就是枚舉每個點作爲重心的情況。由上面重心的定義可以知道,重心一定是最大的子樹大小最小的點。因此,我們不妨先把作爲根,設爲以爲根時子樹的大小,根據dfs的性質,子樹遍歷結束的時候,其也被更新完了,所以,一次從開始的dfs就可以求出所有點的子樹大小。
如果我們用來表示以爲根的最大子樹大小,根據上述算法,dfs結束後可以求出。此時,如果根從換到了的子節點上,該如何變化呢?
這裏滿足了動態規劃的三要素:狀態、邊界條件、轉移方程。
更具體地說,要在樹上所有的點中找到滿足條件的根,就要確定隨根變化的數據(狀態)、初始當根爲的情況(邊界條件)、根從上往下移動的變化(轉移方程)。
來一道題感受一下:
題意:給定一棵所有結點初始爲白色的樹,第一回合選擇任意一個白點塗黑,接下來每次都選和黑點鄰接的白點塗黑,每次(包括第一次)選點時會獲得這個點所在的白點連通塊大小的分數,求可能獲得的最大分數。
這就是換根法的直接應用了。第一個塗黑的點就相當於選一個根,然後沿着根往下走。假設以爲根,表示的子樹大小,爲根時的答案即爲。當根由變爲時,。的最大值就是答案。
#include <bits/stdc++.h>
#define N 200010
#define ll long long
using namespace std;
int n;
vector<int> G[N];
ll sum[N],ans,s;
void dfs1(int u,int fa)
{
for(int v:G[u])
if(v!=fa)
{
dfs1(v,u);
sum[u]=sum[v]+1;
}
s+=sum[u];
}
void dfs2(int u,int fa,ll s)
{
ans=max(ans,s);
for(int v:G[u])
if(v!=fa)
dfs2(v,u,s-2LL*sum[v]+sum[1]);
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=1,u,v;i<n;i++)
{
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs1(1,0);
dfs2(1,0,s);
cout<<ans;
}
睡眠時間不夠,ddl到了作業也沒做,導致這次寫的比較倉促,內容也不是很詳盡,還好算法並不複雜。明、後兩天準備寫一寫數論基礎,尤其是數學角度的推理過程(好像我也不怎麼擅長這些)。