题目https://www.luogu.org/problemnew/show/P2279

题意:一棵树。在节点处建消防站,可以覆盖与他距离在2之内的节点。问最少要建多少个消防站,可以覆盖所有的节点。

思路:有一种贪心的思路,看大部分题解都是这样。

如果要覆盖当前节点(自己不建),那么可能是父亲,兄弟,祖父建了。

但是我们发现,在祖父建覆盖的范围比父亲兄弟要更广一些。所以就贪心的取深度最深的节点,在他的祖父处建一个。

因为想练dp所以没写贪心的。

看结构感觉是树形dp。$dp[i]$表示以$i$为根的子树的情况,想再开一维表示$i$有没有建。后来发现状态好像并不够。

因为只考虑子树的话,当前节点$i$不被覆盖也没关系,他可以被他的父亲或祖先覆盖。

所以大情况分成两种,$i$被覆盖和$i$没被覆盖。

其中$i$被覆盖可以是因为$i$自己建了,也可以是因为有一个儿子建了或者是有一个孙子建了。所以这里有三种状态。

$i$没被覆盖还可以分成只有$i$没被覆盖和$i$和儿子都没有被覆盖。这里又是两种状态。

所以总共是5中状态:

$dp[i][0],在i处建$

$dp[i][1], i处不建但i至少有一个儿子建了$

$dp[i][2],i和儿子都不建但至少有一个孙子建了$

$dp[i][3],自己还没被覆盖,儿子已经被覆盖$

$dp[i][4], 自己和儿子都还没被覆盖$

转移方程:

$dp[i][0] = 1 + \sum min(dp[son][0...4])$每一个儿子的任何一种状态都可以。所以每个儿子都取5种状态的最小的。

$dp[i][1] = min(dp[son1][0] + \sum_(son != son1) min(dp[son][0...3]))$,这里一个巧妙的处理方法是先将每一个儿子的$min(dp[son][0...3])$加上,在找到最小的$dp[son][0]-min(dp[son][0...3])$最后加上。

$dp[i][2] = min(dp[son1][1] + \sum_(son!=son1)(min(dp[son][0...2]))$,此时如果son不在子树被覆盖的话,别的节点也reach不到了。处理方法和上面也一样。

$dp[i][3] = \sum min(dp[son][0...2])$

$dp[i][4] = \sum min(dp[son][0...3]$

 #include<cstdio>
#include<cstdlib>
#include<map>
#include<set>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#include<stack>
#include<queue>
#include<iostream> #define inf 0x3f3f3f3f
using namespace std;
typedef long long LL;
typedef pair<int, int> pr; int n;
const int maxn = ;
int fa[maxn];
vector<int>son[maxn];
int dp[maxn][]; void dfs(int rt)
{
if(son[rt].size() == ){
dp[rt][] = ;
dp[rt][] = dp[rt][] = inf;
dp[rt][] = dp[rt][] = ;
return;
}
dp[rt][] = ;
int maxson = inf, maxgs = inf;
for(int i = ; i < son[rt].size(); i++){
dfs(son[rt][i]);
int tmp1 = inf, tmp2 = inf, tmp3 = inf;
for(int j = ; j < ; j++){
tmp1 = min(tmp1, dp[son[rt][i]][j]);
if(j < )tmp2 = min(tmp2, dp[son[rt][i]][j]);
if(j < )tmp3 = min(tmp3, dp[son[rt][i]][j]);
}
dp[rt][] += tmp1;
dp[rt][] += tmp2;
maxson = min(maxson, dp[son[rt][i]][] - tmp2);
maxgs = min(maxgs, dp[son[rt][i]][] - tmp3);
dp[rt][] += tmp3;
dp[rt][] += tmp3;
dp[rt][] += tmp2;
}
dp[rt][] += maxson;
dp[rt][] += maxgs; } int main()
{
scanf("%d", &n);
for(int i = ; i <= n; i++){
scanf("%d", &fa[i]);
son[fa[i]].push_back(i);
}
dfs();
printf("%d\n", min(dp[][], min(dp[][], dp[][]))); }

最新文章

  1. Linux安装DBI/DBD-ORACLE
  2. 2016暑假多校联合---To My Girlfriend
  3. Cordova - 与iOS原生代码交互2(使用Swift开发Cordova的自定义插件)
  4. js调用ios的方法
  5. transactional replication 的immediate_sync属性
  6. MySQL安装最后一步apply security settings错误
  7. Java 非对称加密
  8. 在浏览器控制台调试php程序
  9. Hive学习笔记【转载】
  10. [CLR via C#]7. 常量和字段
  11. Thrift入门初探--thrift安装及java入门实例
  12. linux上查找文件存放地点和文件中查找字符串方法
  13. GitHub开源:SQLite 增强组件 Sheng.SQLite.Plus
  14. SQL Server之深入理解STUFF
  15. 浅析单点登录,以及不同二级域名下的SSO实现
  16. Linux内核分析 笔记六 进程的描述和进程的创建 ——by王玥
  17. 函数和常用模块【day04】:递归(五)
  18. CURLE_OPERATION_TIMEDOUT libcurl 错误码28– 操作超时
  19. Tensflow预测股票实例
  20. Python: 从字典中提取子集--字典推导

热门文章

  1. Ctrl+Tab
  2. BBC这10部国宝级纪录片,让孩子看遍世间最美的地方
  3. JS将本地图片转化成base64码
  4. VS2010 安装boost库
  5. 版本管理——Git和SVN的介绍及其优缺点
  6. 20191011-构建我们公司自己的自动化接口测试框架-Util的AssertResult模块
  7. js判断是哪种浏览器和阻止页面加载
  8. EF中DbContext的生命周期
  9. Linux增加虚拟内存
  10. git基本命令总结