P5666 樹的重心題解

2022-06-18 13:48:13 字數 1827 閱讀 3888

很容易打出乙個 \(o(n)\)列舉邊 再 \(o(n)\) 求重心 的\(o(n^2)\)的演算法

期望得分:40 points

(其實分析鏈和完美二叉樹可以與暴力一共拿到75分)

正解:整體複雜度\(o(n log n)\)

給出結論:

結論:一棵以 x 為根的樹的重心,一定在 x 的重兒子所構成的集合中,而所有重兒子就構成了一條重鏈,

所以整個結論就是:一棵以 x 為根的樹的重心,一定在 x 向下的重鏈上

因為整棵樹是長這樣的:(重鏈用綠色標出)

首先對於這條重鏈上的結點,它的子樹珂以分為兩類,

乙個是它本身的兒子,乙個是往父親走的兒子,

第一類的子樹size本身珂以處理出來

第二類的子樹size 珂以用 size[root]-size[x]算出,

因為關注重心只用關注最大的子樹,因為一類size是從 葉子->root 遞增的,二類 size是從 葉子->root遞減的

所以考慮倍增處理

設乙個狀態 \(f[x][t]\) 為 重鏈上的結點 x 向下走 \(2^t\) 步 珂以到達的結點

從大到小列舉 t 向下跳就好了,

考慮擁有兩個重心的情況,首先這兩個重心一定是相鄰的(受到鏈和完美二叉樹資料的啟發得出的結論),所以只需要跑到最下面的重心判斷他的fa就好了

然後對於每個結點的每一條出邊跑一次\(o(logn)\)的倍增答案判定就好了

因為在**中我們規定了乙個根節點 1 ,那麼我們在從上到下掃瞄出邊的時候,每一條出邊的終點其實就是它在這個有根樹下的兒子,這個兒子如果刪掉了它的父親,它的重鏈是不會改變的所以可以直接倍增統計,

而對於它的父結點:

會分兩種情況:

第一種斷開了重鏈會改變,顯然斷開的是它的重兒子,那麼斷開後的重鏈顯然是它的次重鏈。

第二種不變就可以直接統計了。

code:

#include using namespace std;

#define ll long long

const int n=3e5+10,logn=20;

int n,siz[n],f[n][logn],son[n],fa[n];

ll ans;

int t,head[n],to[n<<1],next[n<<1],tot;

inline int read()ch=getchar();}

while(isdigit(ch))

return x*f;

}inline void add(int u,int v)

inline void get_son(int x)

if(siz[u]*2==siz[x]) ans+=fa[u];//統計兩個重心的情況

ans+=u;

return;

}inline void dfs(int x,int ff)

} f[x][0]=son[x];get_son(x);

}inline void getans(int x,int ff)

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

} f[x][0]=son[x];

get_son(x),fa[x]=ff;

}inline void myclear()

int main()

dfs(1,0),getans(1,0);

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

} return 0;

}

洛谷 P5666 樹的重心

關於樹的重心有一條性質,假如 u 不是重心,那麼重心一定在 u 的size最大的子樹中.利用這個性質我們可以利用倍增的思想快速找到重心.即維護 jump u,i 表示從 u 出發,向重兒子走 2 i 步所到達的節點.由於要對於每條邊統計,所以用換根dp的思想,在dfs的時候維護以當前節點為根時的 j...

洛谷 P1395 會議(樹的重心)

為什麼要找樹的重心呢?假設我們已經找到了樹的重心,如果把開會地點從重心向右移走,那麼對答案的貢獻就是 左邊的元素和 右邊的元素和。而因為是樹的重心,所以向右移走後左面的元素數量一定大於右面的元素數量,所以ans是比在重心的情況大的,所以最終我們選擇重心。include include include...

求樹的重心

題目 題意 給定一棵樹,求樹的重心的編號以及重心刪除後得到的最大子樹的節點個數size,如果size相同就選取編號最小的.分析 首先要知道什麼是樹的重心,樹的重心定義為 找到乙個點,其所有的子樹中最大的子樹節點數最少,那麼這個點就是這棵 樹的重心,刪去重 心後,生成的多棵樹盡可能平衡.實際上樹的重心...