題意
某個公司有 $ n $ 個人,上下級關係構成了一個有根樹。其中有個人是叛徒(這個人不知道是誰)。對於一個人, 如果他下屬(直接或者間接, 不包括他自己)中叛徒佔的比例超過 $ x $ ,那麼這個人也會變成叛徒,並且他的所有下屬都會變成叛徒。你要求出一個最小的 $ x $ ,使得最壞情況下,叛徒的個數不會超過 $ k $ 。$ (1 \leq k \leq n \leq 500000) $
題解
首先有一個顯然的二分做法,然而不知道為什麼會被卡……
於是考慮能不能 $ O(n) $ 做。
首先有兩個結論:
- 最壞情況下,最初的那一個叛徒一定是葉子結點。
- 如果一個非葉子節點叛變,則它其中的一個兒子一定叛變
所以最終所有的叛徒一定是原樹中的一棵子樹。
$ f[i] $ 表示要使節點 $ i $(及其下屬)恰好不能叛變的最小比例 $ x $
因為所有 $ size[i] > k $ 的子樹都不能叛變
所以 $ ans = max(f[i]) \quad (size[i] > k) $
然後考慮怎樣求 $ f[i] $
$ f[i] $ 也等價於恰好讓 $ i $ 叛變的最大比例 $ x $
對於每一個 $ i $ ,首先列舉它的哪個子樹叛變了(假設是 $ j $ )
那麼想讓 $ i $ 叛變有兩個條件:
- 比例 $ x $ 滿足能夠讓 $ j $ 叛變
- 比例 $ x $ 滿足 $ \dfrac{size[j]}{size[i] - 1} > x $
所以 $ f[i] = max(f[i], min(f[j], \dfrac{size[j]}{size[i]-1})) $
邊界條件為:$ f[i] = \begin{cases} 1 \quad \text{葉子結點} \\ 0 \quad \text{非葉子節點} \end{cases} $
AC Code
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#define MAX_N 500005
using namespace std;
int n,k;
int siz[MAX_N];
double ans=0;
double f[MAX_N];
vector<int> edge[MAX_N];
void read()
{
scanf("%d%d",&n,&k);
int x;
for(int i=2;i<=n;i++)
{
scanf("%d",&x);
edge[x].push_back(i);
}
}
void dfs(int x)
{
siz[x]=1,f[x]=(edge[x].size()==0);
for(int i=0;i<edge[x].size();i++)
{
int t=edge[x][i];
dfs(t),siz[x]+=siz[t];
}
for(int i=0;i<edge[x].size();i++)
{
int t=edge[x][i];
f[x]=max(f[x],min(f[t],siz[t]/(siz[x]-1.0)));
}
if(siz[x]>k) ans=max(ans,f[x]);
}
void work()
{
dfs(1);
printf("%.9f\n",ans);
}
int main()
{
read();
work();
}