Hile每日算法-3.31-樹形dp之換根法

樹形dp之換根法

週二週三真的太難了,有早課導致不能熬夜,於是就只能趁着中午的時間寫一寫,這幾天先寫點簡單的東西,就當重新複習了,應該算是給初學者的知識普及,其他的過了週三再說。

首先來講一下樹的重心

樹的重心,即 樹上到所有點的距離之和最小/以此爲根深度最小/最大子樹大小最小 的點,具有很多方便的性質,如:

1.當一棵樹添加/刪除一個節點,樹的重心最多移動一個位置。(動態維護)(19icpc徐州M題,so~no~chi~no~sa~da~me~)

2.當兩棵樹通過某點連接在一起形成新樹時,新樹的重心一定在連接兩棵舊樹重心的路徑上。(兩樹合併)

3.以一顆樹的重心爲根,劃分的子樹大小一定不超過原樹的一半。(樹分治)

衆所周知,回字有四種寫法,門前有兩棵棗樹,重心也有兩種求法(通常情況下),一種是兩遍dfs找樹上最長路徑的中點,一種就是今天要講的內容——樹形dp之換根法。

首先,初始是不知道重心所在的,最直接的方法就是枚舉每個點作爲重心的情況。由上面重心的定義可以知道,重心一定是最大的子樹大小最小的點。因此,我們不妨先把11作爲根,設s[i]s[i]爲以11爲根時子樹ii的大小,根據dfs的性質,子樹遍歷結束的時候,其s[i]s[i]也被更新完了,所以,一次從11開始的dfs就可以求出所有點的子樹大小。

如果我們用f[i]f[i]來表示以ii爲根的最大子樹大小,根據上述算法,dfs結束後可以求出f[1]=max(s[son])f[1]=max(s[son])。此時,如果根從11換到了11的子節點上,f[i]f[i]該如何變化呢?

f[i]=max(s[son],ns[i])f[i]=max(s[son],n-s[i])

這裏滿足了動態規劃的三要素:狀態邊界條件轉移方程

更具體地說,要在樹上所有的點中找到滿足條件的根,就要確定隨根變化的數據(狀態)、初始當根爲11的情況(邊界條件)、根從上往下移動的變化(轉移方程)。

來一道題感受一下:

CF1187E Tree Painting

題意:給定一棵所有結點初始爲白色的樹,第一回合選擇任意一個白點塗黑,接下來每次都選和黑點鄰接的白點塗黑,每次(包括第一次)選點時會獲得這個點所在的白點連通塊大小的分數,求可能獲得的最大分數。

這就是換根法的直接應用了。第一個塗黑的點就相當於選一個根,然後沿着根往下走。假設以11爲根,s[i]s[i]表示ii的子樹大小,11爲根時的答案ans1ans_1即爲i=1ns[i]\sum_{i=1}^ns[i]。當根由uu變爲vv時,ansv=ansus[v]+(s[1]s[v])=ansu2s[v]s[1]ans_v=ans_u-s[v]+(s[1]-s[v])=ans_u-2*s[v]-s[1]ansians_i的最大值就是答案。

#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到了作業也沒做,導致這次寫的比較倉促,內容也不是很詳盡,還好算法並不複雜。明、後兩天準備寫一寫數論基礎,尤其是數學角度的推理過程(好像我也不怎麼擅長這些)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章