P2279 消防局的设立 (树形DP or 贪心)
树形DP写法
看到这个题的要求,很容易相到这是一个树形DP的问题,但是dp数组应该如何设计并转移才是关键
dp[i][0]代表当前结点可以向上覆盖2层,自身一定被覆盖
dp[i][1]代表当前结点可以向上覆盖1层,自身一定被覆盖
dp[i][2]代表当前结点可以向上覆盖0层,自身一定被覆盖
dp[i][3]代表当前结点可以向下覆盖1层,表示自己不一定被覆盖,但是儿子一定全部被覆盖
dp[i][4]代表当前结点可以向下覆盖2层,表示自己不一定被覆盖,但是孙子一定全部被覆盖
所谓向上覆盖x层,即当前结点向上x个结点(祖先结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
所谓向下覆盖x层,即当前结点向下x个结点(后代结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
显然满足
dp[i][0] >= dp[i][1] >= dp[i][2] >= dp[i][3] >= dp[i][4]
dp[u][0] = 1 + min(dp[v][0~4])
如果当前结点想要覆盖向上2层,则自身必然安置一个消防站,而u所有子节点就随意了
dp[u][1] = min{ dp[v][0] + ∑dp[e][0~3] , dp[u][0] }
覆盖到当前结点上1层,有两种情况:
1)在其子节点安置了至少一个消防站,这样一来,不仅覆盖了当前结点上一层的结点,而且
安置了消防站的子节点将覆盖他的兄弟结点,所以其兄弟结点至少保证自身子节点被完全覆盖即可
2)当前结点安置消防站,覆盖了上两层的同时,也可以覆盖当前结点上一层
dp[u][2] = min{dp[v][1] + ∑dp[e][2],dp[u][1],dp[u][0]}
覆盖到当前结点上0层,分三种情况
1)刚好覆盖到当前结点,则至少选择一个当前结点的孙子结点,由于这个消防站无法覆盖到当前结点的其他子节点,
那么其余结点至少保证自身被覆盖即可
2)可以覆盖到当前结点向上一层,同时覆盖了当前结点的子孙结点,此时即在当前结点的子节点设立消防站
3)可以覆盖到当前结点向上二层,同时覆盖了当前结点的子孙结点,此时即在当前结点设立消防站
dp[u][3] = ∑dp[v][2]
覆盖当前结点所有的子节点,则保证当前结点的所有子节点被覆盖
dp[u][4] = ∑dp[v][3]
覆盖当前结点所有的孙子节点,则保证当前结点的所有子节点的子节点被覆盖
最后,为了覆盖整棵树,我们输出dp[1]0]即可
代码区
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip> #define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 1e9 + ;
const int mod = 1e9 + ;
const int Max = 1e3 + ; int n, m, k;
int head[Max], tot;
int to[Max], Next[Max];
int dp[Max][]; /*
* dp[i][0]代表当前结点可以向上覆盖2层,自身一定被覆盖
* dp[i][1]代表当前结点可以向上覆盖1层,自身一定被覆盖
* dp[i][2]代表当前结点可以向上覆盖0层,自身一定被覆盖
* dp[i][3]代表当前结点可以向下覆盖1层,表示自己不一定被覆盖,但是儿子一定全部被覆盖
* dp[i][4]代表当前结点可以向下覆盖2层,表示自己不一定被覆盖,但是孙子一定全部被覆盖
*
* 所谓向上覆盖x层,即当前结点向上x个结点(祖先结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
* 所谓向下覆盖x层,即当前结点向下x个结点(后代结点)作为根结点的树被完全覆盖的情况下,最少需要设立的消防站数目
*
* 显然满足
* dp[i][0] >= dp[i][1] >= dp[i][2] >= dp[i][3] >= dp[i][4]
*
* dp[u][0] = 1 + min(dp[v][0~4])
* 如果当前结点想要覆盖向上2层,则自身必然安置一个消防站,而u所有子节点就随意了
*
* dp[u][1] = min{ dp[v][0] + ∑dp[e][0~3] , dp[u][0] }
* 覆盖到当前结点上1层,有两种情况:
* 1)在其子节点安置了至少一个消防站,这样一来,不仅覆盖了当前结点上一层的结点,而且
* 安置了消防站的子节点将覆盖他的兄弟结点,所以其兄弟结点至少保证自身子节点被完全覆盖即可
* 2)当前结点安置消防站,覆盖了上两层的同时,也可以覆盖当前结点上一层
*
* dp[u][2] = min{dp[v][1] + ∑dp[e][2],dp[u][1],dp[u][0]}
* 覆盖到当前结点上0层,分三种情况
* 1)刚好覆盖到当前结点,则至少选择一个当前结点的孙子结点,由于这个消防站无法覆盖到当前结点的其他子节点,
* 那么其余结点至少保证自身被覆盖即可
* 2)可以覆盖到当前结点向上一层,同时覆盖了当前结点的子孙结点,此时即在当前结点的子节点设立消防站
* 3)可以覆盖到当前结点向上二层,同时覆盖了当前结点的子孙结点,此时即在当前结点设立消防站
*
* dp[u][3] = ∑dp[v][2]
* 覆盖当前结点所有的子节点,则保证当前结点的所有子节点被覆盖
*
* dp[u][4] = ∑dp[v][3]
* 覆盖当前结点所有的孙子节点,则保证当前结点的所有子节点的子节点被覆盖
*
* 最后,为了覆盖整棵树,我们输出dp[1]0]即可
*/ void add(int u, int v)
{
to[tot] = v;
Next[tot] = head[u];
head[u] = tot++;
} void dfs(int u)
{
dp[u][] = ;
dp[u][] = inf;
dp[u][] = inf;
dp[u][] = ;
dp[u][] = ;
for (int i = head[u]; i != -; i = Next[i])
{
int v = to[i];
dfs(v);
dp[u][] += dp[v][];
dp[u][] += dp[v][];
dp[u][] += dp[v][];
}
if (head[u] == -) //没有子节点了,此时dp[u][0~2]必须使得u安置消防站
{
dp[u][] = dp[u][] = ;
return;
}
for (int i = head[u]; i != -; i = Next[i])
{
int v = to[i]; int sum1 = , sum2 = ; //记录∑dp[e][3]和∑dp[e][4]
for (int j = head[u]; j != -; j = Next[j])
{
int e = to[j];
if (e == v)
continue;
sum1 += dp[e][];
sum2 += dp[e][];
}
dp[u][] = min(dp[u][], dp[v][] + sum1);
dp[u][] = min(dp[u][], dp[v][] + sum2);
}
for (int i = ; i <= ; i++) //最后综合处理一下
dp[u][i] = min(dp[u][i], dp[u][i - ]);
} int main()
{
#ifdef LOCAL
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
#endif
memset(head, -, sizeof(head));
tot = ; scanf("%d", &n);
for (int v = , u; v <= n; v++)
scanf("%d", &u), add(u, v);
dfs();
printf("%d\n", dp[][]);
return ;
}
贪心写法(适用于在树种,求点覆盖半径为k的最小点覆盖)
其实这种解法对于这一类型的题目非常的适合,主要体现在不需要像树形DP一样确定状态并且易于理解,这种解法的思想如下:
我们用dis[i]表示结点i到最近的消防站的最短距离,如果dis[i] > k ,说明结点i不在已存在的消防站的覆盖范围内,为了保证消防站利用率最大化,我们在结点i向上第k个结点,记作x,也就是结点i覆盖的极限范围出处设立消防站,这样一来不仅使得结点i被覆盖,还可以覆盖更多的结点,然后我们更新x向上k层范围内所有结点的dis,即更新各点到消防站的最近距离,重复这一过程,统计消防站的数目即可
代码区
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<string>
#include<fstream>
#include<vector>
#include<stack>
#include <map>
#include <iomanip> #define bug cout << "**********" << endl
#define show(x, y) cout<<"["<<x<<","<<y<<"] "
#define LOCAL = 1;
using namespace std;
typedef long long ll;
const int inf = 1e9 + ;
const int mod = 1e9 + ;
const int Max = 1e3 + ; struct Node
{
int depth; //记录当前结点深度
int id; //记录结点编号
} node[Max]; int n, k;
int dis[Max]; //dis[i]记录结点i到最近的消防站的最近距离
int fa[Max]; //dp[i]记录i的父节点 bool cmp(Node node1, Node node2)
{
return node1.depth > node2.depth;
} int main()
{
#ifdef LOCAL
// freopen("input.txt", "r", stdin);
// freopen("output.txt", "w", stdout);
#endif
scanf("%d", &n); k = ; //结点覆盖半径为2,根据实际情况改变 node[].depth = ;
node[].id = ;
dis[] = dis[] = inf; //处理根结点(这里灵活建图即可) for (int v = , u; v <= n; v++)
{
scanf("%d", &u); //构建的边为 u --> v
node[v].depth = node[u].depth + ;
node[v].id = v;
fa[v] = u;
dis[v] = inf;
}
sort(node + , node + + n, cmp);
int sum = ; for (int i = ; i <= n; i++)
{
int son = node[i].id;
int now = node[i].id; //当前结点 for (int j = ; j <= k; j++) //处理出k个祖先结点到当前结点的最佳距离
{
now = fa[now];
dis[son] = min(dis[son], dis[now] + j);
} if (dis[son] > k) //代表每个结点覆盖半径k
{
dis[now] = ; //此处总是在最远祖先处设立消防站
sum++;
for (int j = ; j <= k; j++) //之后由最远祖先结点向上k层更新其余点的距离
{
now = fa[now];
dis[now] = min(dis[now], j);
}
}
}
printf("%d\n", sum);
return ;
}
P2279 消防局的设立 (树形DP or 贪心)的更多相关文章
- P2279 [HNOI2003]消防局的设立[树形dp]
题目描述 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状 ...
- 【BZOJ1217】[HNOI2003]消防局的设立 树形DP
[BZOJ1217][HNOI2003]消防局的设立 Description 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地, ...
- [HNOI2003]消防局的设立 树形dp // 贪心
https://www.luogu.org/problemnew/show/P2279 一开始就想到了贪心的方法,不过一直觉得不能证明. 贪心的考虑是在深度从深到浅遍历每个结点的过程中,对于每个没有覆 ...
- bzoj1217: [HNOI2003]消防局的设立 [树形dp]
Description 2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地.起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了 ...
- luogu 2279 [HNOI2003]消防局的设立 树形dp
就是细节多一些,思路都非常常规. Code: #include <bits/stdc++.h> #define N 1005 #define inf 1061109567 #define ...
- [HNOI2003] 消防局的设立 - 树形dp
仍然是点覆盖集问题,但覆盖半径变成了\(2\) 延续上一题的思路,只是式子更加复杂了 想体验一下min_element大法于是不想优化了 #include <bits/stdc++.h> ...
- 洛谷 P2279 [HNOI2003]消防局的设立 (树形dp or 贪心)
一看到这道题就知道是树形dp 之前做过类似的题,只不过保护的范围是1 所以简单很多. 这道题保护的范围是2,就复杂了很多. 我就开始列状态,然后发现竟然有5种 然后我就开始列方程. 但是我考虑的时候是 ...
- 洛谷P2279 消防局的设立【树形dp】
题目:https://www.luogu.org/problemnew/show/P2279 题意:一棵树.在节点处建消防站,可以覆盖与他距离在2之内的节点.问最少要建多少个消防站,可以覆盖所有的节点 ...
- 【题解】P2279消防局的设立
[题解][P2279 HNOI2003]消防局的设立 又是一道贪心. 随便指定一个点为根,可以知道在覆盖了一个节点的子树的情况下,消防站越高越好.那么我们就贪心吧.\(trick\)是按深度\(pus ...
随机推荐
- 数据结构实验之栈与队列三:后缀式求值(SDUT 2133)
题解:把每一步计算的答案再存在栈里面,直到计算结束. 如果是操作数 那么直接入栈:如果是运算符,那么把栈里面最顶部的两个操作数拿出来进行运算,运算结果再放入到栈里面,计算完所有的(#之前的长度位len ...
- Simple Problem with Integers(POJ 3486)
A Simple Problem with Integers Time Li ...
- Apache Web服务器 安装步骤 和遇到的坑
Apache Web服务器是开发放源码的网页服务器,我们看到的网页都是上传到服务器然后呈现给用户的. 在开发中,在自己的电脑上安装Apache Web服务器,你的电脑也会成为服务器,配置文件,访问你的 ...
- java安全学习-环境准备/基础知识
补java的坑,开始! 1.Intellij一些快捷键 intell常用快捷键: ctrl+n 快速查找定位类的位置 ctrl+q 快速查看某个类的文档信息 shift + F6 快速类.变量重命名 ...
- 《梁宁·产品思维30讲》课程学习笔记(内含全套音频+ppt资料
科技进步.产品迭代.公司演化.组织变迁……不变的是用户的情绪和人性. 那些信奉“用户驱动”的人,从普通人变成了行业大佬,建立了自己的世界.乔布斯.马化腾.马云.雷军.张小龙.周鸿祎.傅盛……这些改变世 ...
- 前端知识点回顾——koa和模板引擎
koa 基于Node.js的web框架,koa1只兼容ES5,koa2兼容ES6及以后. const Koa = requier("koa"); const koa = new K ...
- Java同步数据结构之ConcurrentLinkedQueue
前言 前面介绍的Queue都是通过Lock锁实现的阻塞队列,今天介绍一种非阻塞队列ConcurrentLinkedQueue,所谓非阻塞,其实就是通过CAS代替加锁来实现的高效的非阻塞队列.当许多线程 ...
- PLSQL 经常自动断开失去连接的解决过程
转: PLSQL 经常自动断开失去连接的解决过程 问题背景: 情况是这样的,很多开发同事的PLSQL上班时间开着8个小时,有时候他们出去抽烟后或者中午吃完饭,回来在PLSQL上面执行就报错无响应,然后 ...
- jmeter-显示log的方法,和脚本通用的语法
beanshell log日志设置.log日志输出 步骤: 1.从选项-勾选Log Viewer,打开调试窗口 2.选择显示log的等级 3.在脚本中加入要打引的log 如: log.info(‘日 ...
- RabbitMQ运转流程
生产者发送消息的过程 生产者连接到RabbitMQ Broker(相当于是一个RabbitMQ服务器),建立一个连接(Connection),开启一个信道(Channel). 生产者声明一个交换器(E ...