1. 程式人生 > >BZOJ 4726 [POI2017]Sabota?:樹形dp

BZOJ 4726 [POI2017]Sabota?:樹形dp

能夠 如果 define lin 傳送門 ble span HP source

傳送門

題意

某個公司有 $ 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(); }

BZOJ 4726 [POI2017]Sabota?:樹形dp