很容易打出乙個 \(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相同就選取編號最小的.分析 首先要知道什麼是樹的重心,樹的重心定義為 找到乙個點,其所有的子樹中最大的子樹節點數最少,那麼這個點就是這棵 樹的重心,刪去重 心後,生成的多棵樹盡可能平衡.實際上樹的重心...