其他分享
首页 > 其他分享> > P3000 [USACO10DEC]Cow Calisthenics G

P3000 [USACO10DEC]Cow Calisthenics G

作者:互联网

二分 + \(dp\) 的好题。

首先,要求最大直径最小,我们肯定会想到二分最大直径。 难点就在于怎么 \(check\)。由于正确性过于显然,二分可行性证明就略了吧。

记录数组 \(dp_u\) 表示在 \(u\) 号点,其子树中所有点到达 \(u\) 号点的最大链长。转移时,考虑其和子树之间的关系。

可以发现,\(u\) 号点就类似于一个 \(lca\),两个最长的儿子拼起来的最长链就是这棵子树的最大直径。注意,这里的 \(dp_v\) 中所存最长链均已保证子树内部合法。

接着考虑子树中直径超过二分的上限怎么办?贪心地想,我们肯定是要断掉最长的那条链,保留次长的。因为这样可以使得我们的总长度尽可能小,从而达到最优次数。
最后,如果必须断边的次数大于上限 \(s\), 则不合格,应该调大区间,否则一定合格。想一想为什么。(题目中不是要求我们断掉 \(s\) 条边吗

具体实现看代码:

#include <bits/stdc++.h>
using namespace std;
#define N 1000010
const int inf = 2e9; 

template <class T>
inline void read(T& a){
    T x = 0, s = 1;
    char c = getchar();
    while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); }
    a = x * s;
    return ; 
}

struct node{
    int v, next;
} t[N << 1];
int f[N];

int bian = 0;
inline void add(int u, int v){
    t[++bian] = (node){v, f[u]}, f[u] = bian;
    return ; 
}

int n, s;
int dp[N];
int sum = 0;   // 断了多少次 

void dfs(int now, int father, int lim){
    int maxn = 0, minn = inf; 
    for(int i = f[now]; i; i = t[i].next){
        int v = t[i].v;
        if(v == father) continue; 
        dfs(v, now, lim); 
        if(dp[v] + maxn > lim) sum++, maxn = min(maxn, dp[v]);   // 保留最小值
        else maxn = max(maxn, dp[v]);     // 否则更新成最大值
    }
    dp[now] = maxn + 1; 
    return ; 
}

bool check(int lim){
    sum = 0;   // 记得清空 
    dfs(1, 0, lim);
    return sum <= s; 
}

int main(){
    read(n); read(s); 
    for(int i = 1; i < n; i++){
        int x, y;
        read(x), read(y);
        add(x, y); add(y, x); 
    }
    int l = 1, r = n, ans = 0;   // 最多断 n 次 
    while(l <= r){
        int mid = l + r >> 1;
        if(check(mid)) ans = mid, r = mid - 1;
        else l = mid + 1; 
    }
    cout << ans << endl;
    return 0; 
}

标签:Calisthenics,二分,P3000,mid,USACO10DEC,maxn,号点,return,dp
来源: https://www.cnblogs.com/wondering-world/p/14075104.html