Luogu3554 [POI2013]LUK-Triumphal arch(二分答案+树形DP)题解

题意补充

  • 事实上,(A)每次能染的(k)个节点并不一定与(B)所在节点相邻,换言之,(A)可以提前染一些节点使得(B)以后走不到。
  • 数据范围:(1 leq n leq 300000)

算法

二分答案 + 树形DP

思路

二分每次染的节点个数(k),再用树形DP检查;

(f_u)为以(u)节点为根的子树(不含(u))全部染完除了(k)以外还需要的次数,那么:

[f_u = max(sum{f_v} ; +siz_u - k, 0) ]

其中,(siz_u)(u)的儿子数量。最后当(f_1 = 0)时即为合法。

注意要从儿子往父亲推,这样才能避免不知道先处理哪一个儿子的情况。

为什么贪心是错的:

考虑这种情况:有一个节点有大量儿子,而它的兄弟节点们只有一个儿子,显然,贪心是不好处理的。

参考代码

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 3e5 + 10;
int n,head[maxn << 1],num;
struct Edge{
    int then,to;
}e[maxn << 1];

void add(int u, int v){e[++num] = (Edge){head[u], v}; head[u] = num;}

int f[maxn];
void DFS(int u, int fa, int ch){
    int cnt = 0;
    for(int i = head[u]; i; i = e[i].then){
        int v = e[i].to;
        if(v != fa){
            cnt ++;
            DFS(v, u, ch);
            f[u] += f[v];
        }
    }
    f[u] = max(0, f[u] + cnt - ch);
}

int check(int p){
    memset(f,0,sizeof(f));
    DFS(1, 1, p);
    return (f[1] <= 0);
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i < n; ++ i){
        int u,v; scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    int l = 0, r = maxn;
    while(l <= r){
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid - 1;
        else l = mid + 1;
    }
    printf("%d
", r + 1);
    return 0;
}

福利数据

7
1 2
1 3
2 5
2 6
7 2
4 1
ans:3
原文地址:https://www.cnblogs.com/whenc/p/13967446.html