嘘~ 正在从服务器偷取页面 . . .

洛谷P3523 [POI2011] DYN-Dynamite 题解


洛谷P3523 [POI2011] DYN-Dynamite 题解

题目链接:P3523 [POI2011] DYN-Dynamite

题意

给定一棵 $n$ 个节点的树,树上有若干个关键点。

现在要你选 $m$ 个点,使得所有关键点到这 $m$ 个点距离的最小值的最大值 $K$ 最小。

输入格式

第一行给出 $n,m$ ,第二行给出每个点是否是关键点。

接下来 $n-1$ 行,每行两个整数 $u,v$ 表示 $u,v$ 之间有一条边。

输出格式

一行,即 $K$ 的最小值。

数据范围

$1 \le n,m \le 3\times 10^5$ 。

考虑二分 $K$ ,问题转化为能否选 $m$ 个点,使得这 $m$ 个点与关键点的最小距离均不超过 $K$ 。

可以发现放的点越多,这个最小距离越不可能超过 $K$ 。

于是问题就转化为,选 $k$ 个点,使得这 $k$ 个点与关键点的最小距离均不超过 $K$ ,最小化 $k$ 。

不妨称点 $u$ 覆盖了关键点 $v$ ,当且仅当点 $u$ 到关键点 $v$ 的距离不超过 $K$ 。

注意到对于每个关键点,我们会尽可能晚一些用点去覆盖它。

考虑设 $f_u$ 为 $u$ 所在子树中最远的未被覆盖到的关键点,$g_u$ 为 $u$ 到子树中钦定的点的最小距离。

叶节点的初值为 $f_u = -\infty,~g_u=+\infty$ ,其余节点的初值为

接着考虑需要钦定点的情况,以及边界情况:

  • 若 $f_u + g_u \le K$ ,则不需要钦定点,令 $f_u \gets -\infty$ 即可。
  • 若 $f_u = K$ ,则我们必须要钦定 $u$ 来覆盖它,那么 $f_u \gets -\infty,\,g_u \gets 0,\,k\gets k+1$ 。
  • 若 $g_u > K$ 且 $u$ 是关键点,直接令 $f_u \gets \max\{f_u,0\}$ 即可。

这样点 $u$ 的子树被完全覆盖当且仅当 $f_u=-\infty$ ,记得特判根节点的决策。

时间复杂度 $\mathcal{O}(n \log n)$

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x, int y) { x < y ? x = y : 0; }
void down(int &x, int y) { x > y ? x = y : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define pb push_back
#define N ((int)(3e5 + 15))

bool vip[N]; vector<int> vec[N];
int n, m, cnt, f[N], g[N];
void dfs(int u, int fa, int mid)
{
    f[u] = -INF; g[u] = INF;
    for(int v : vec[u]) if(v != fa)
    {
        dfs(v, u, mid);
        up(f[u], f[v] + 1); down(g[u], g[v] + 1);
    }
    if(f[u] + g[u] <= mid) f[u] = -INF;
    if(g[u] > mid && vip[u]) up(f[u], 0);
    if(f[u] == mid) { f[u] = -INF; g[u] = 0; ++cnt; }
}
bool check(int mid)
{
    cnt = 0; dfs(1, 0, mid);
    if(f[1] >= 0) ++cnt;
    return cnt <= m;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    cin >> n >> m; rep(i, 1, n) cin >> vip[i];
    rep(i, 1, n - 1)
    {
        static int u, v; cin >> u >> v;
        vec[u].pb(v); vec[v].pb(u);
    }
    int l = 0, r = n;
    while(l < r)
    {
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid; else l = mid + 1;
    }
    cout << l << '\n';
    return 0;
}

参考文献

[1] https://www.luogu.com.cn/article/i7w2evqa


题外话

放图片。


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录