EOJ Monthly 2020.9 B健康监测计划【贪心 | 思维 | 树形DP】

8 篇文章 0 订阅
2 篇文章 0 订阅

题目链接

题目描述:

华东师范大学共有 n 栋楼房,编号为 1, 2, …, n,并有 n − 1 条双向联通的道路连接这些楼房,使得任意两栋楼房之间有且仅有一条简单路径(一条简单路径是指,从一栋大楼出发,不经过重复大楼,并在另一栋大楼结束的路径)。学校为了贯彻落实常态化疫情防控政策,决定选择一些楼房,在其中各设置一个健康监测点,实时监测路过的同学的健康状况。
虽然自动化检查仪器已经在全校普及,但是监测的过程依然不可避免地会影响学生的出入便捷性。所以学校需要精心安排监测点的位置,使得监测点数量尽可能多,而且尽量不对师生们造成太大的麻烦。具体而言,对于任意一条从某一栋楼开始在另一栋楼结束的简单路径,你需要保证路径上的监测点的数量不超过 k(包括起点和终点上的监测点),并在此前提下最大化监测点的数量。最终要输出最大化监测点的数量。


感受:

久违的EOJ月赛~
这道题比赛时总是过不了,我以为我的做法出了问题,赛后发现原来是有一种特殊情况忘记判断了。。
以下先奉上题解做法和我的做法


题解:

做法1:标准题解

考虑 k = 2 的时候可取所有的叶子结点,接着考虑 k = 3 的时候,发现可以多取一个点,考虑 k = 4 的时候,最优策略一定是,先取所有的叶子结点并去掉它们,在剩余的树中取叶子结点。于是就有了一个想法,是不是对于 k 为偶数的时候,就是取 k/2 次叶子,当 k 为奇数的时候,多取一个结点。
大概可以证明一下,显然前一批次取的叶子结点一定要比后一批的叶子结点更多,调整法可知一定是最后几层叶子,又因为一条简单路径最多经过两个同批次取的叶子结点,所以显然是可行的。

技巧:

分层剥离叶子结点时可以使用两个队列,具体看代码,其中用到了std::move,std::move是为性能而生,std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。

注意事项:

当 k 为奇数时,可以多取一个点,但是如果最后加上这个点后超过 n,此时就不应该加了,这个地方也是我自己做法被坑的地方。

做法2:本人做法

通过树形dp维护 one 和 two 两个数组,分别表示一个结点最大层数(层数指的是该点距离叶子结点的距离,叶子结点的层数为1)和次大层数。当一个结点的最大和次大层数都大于 k/2 时,则这个点不能设立检查站。
简单证明一下我的做法:若一个点 x 的最大和次大层数都大于 k/2 ,当这个点 x 在一对叶子结点的简单路径上时,我们知道分别从两端叶子结点沿着这条简单路径出发已经分别设立了 k/2 个检查站,而 x 点不在这个范围内,所以 x 不能设立检查站。


做法1 AC Codes:

 #include <iostream>
 #include <vector>
 #include <queue>
 #include <stack>
 #include <set>
 #include <map>
 //#include <unordered_set>
 //#include <unordered_map>
 #include <deque>
 #include <list>
 #include <iomanip>
 #include <algorithm>
 #include <fstream>
 #include <cmath>
 #include <cstdio>
 #include <cstring>
 #include <cstdlib>
 //#pragma GCC optimize(2)
 using namespace std;
 typedef long long ll;
 //cout << fixed << setprecision(2);
 //cout << setw(2);
 const int N = 1e6 + 6, M = 1e9 + 7;

 int k, n, vis[N], d[N];
 vector<int> g[N];

 int main() {
     //freopen("/Users/xumingfei/Desktop/ACM/test.txt", "r", stdin);
     ios::sync_with_stdio(false);
     cin.tie(0), cout.tie(0);
     cin >> n >> k;
     int x, y;
     for (int i = 1; i < n; i++) {
         cin >> x >> y;
         g[x].push_back(y), g[y].push_back(x);
         d[x]++, d[y]++;
     }
     int ans = 0;
     if (k == 1) {
         ans = 1;
     } else {
         if (k & 1) ans++;
         k /= 2;
         queue<int> q1, q2;
         for (int i = 1; i <= n; i++) {
             if (d[i] == 1) {
                 q1.push(i);
                 vis[i] = 1;
             }
         }
         while (k > 0) {
             while (!q1.empty()) {
                 int x = q1.front();
                 q1.pop();
                 ans++;
                 for (int y : g[x]) {
                     if (vis[y]) continue;
                     if (--d[y] == 1) q2.push(y), vis[y] = 1;
                 }
             }
             q1 = move(q2);
             k--;
         }
     }
     if (ans > n) ans = n;
     cout << ans << '\n';
     return 0;
 }

做法2 AC Codes:

#include <iostream>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
//#include <unordered_set>
//#include <unordered_map>
#include <deque>
#include <list>
#include <iomanip>
#include <algorithm>
#include <fstream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
//#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
//cout << fixed << setprecision(2);
//cout << setw(2);
const int N = 1e6 + 6, M = 1e9 + 7;

int n, k, one[N], two[N];
int head[N], Next[N << 1], ver[N << 1], tot;
void add(int u, int v) {
    ver[++tot] = v;
    Next[tot] = head[u], head[u] = tot;
}

void dfs1(int x, int fa) {
    int sum = 0;
    for (int i = head[x]; i; i = Next[i]) {
        int y = ver[i];
        if (y == fa) continue;
        sum++;
        dfs1(y, x);
        int now = one[y] + 1;
        if (now > one[x]) {
            two[x] = one[x];
            one[x] = now;
        } else if (now <= one[x] && now > two[x]) {
            two[x] = now;
        }
    }
    if (sum == 0) {
        one[x] = 1;
    } else if (sum == 1 && fa == 0) {
        two[x] = 1;
    }
}

void dfs2(int x, int fa) {
    for (int i = head[x]; i; i = Next[i]) {
        int y = ver[i];
        if (y == fa) continue;
        int now = one[x];
        if (now == one[y] + 1) now = two[x] + 1;
        if (now > one[y]) {
            two[y] = one[y];
            one[y] = now;
        } else if (now <= one[y] && now > two[y]) {
            two[y] = now;
        }
        dfs2(y, x);
    }
}

int main() {
    //freopen("/Users/xumingfei/Desktop/ACM/test.txt", "r", stdin);
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n >> k;
    tot = 1;
    int x, y;
    for (int i = 1; i < n; i++) {
        cin >> x >> y;
        add(x, y), add(y, x);
    }
    if (n == 1) {
        cout << 1 << '\n';
    } else {
        dfs1(1, 0);
        dfs2(1, 0);
        int ans = 0;
        if (k % 2 == 1) {
            k--;
            ans++;
        }
        k /= 2;
        for (int i = 1; i <= n; i++) {
            if (one[i] - 1 >= k && two[i] - 1 >= k) {
                ;
            } else {
                ans++;
            }
        }
        if (ans > n) ans = n;
        cout << ans << '\n';
    }
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值