[HNOI]2003 消防局的建立

来源:互联网 发布:约瑟夫环c语言链表 编辑:程序博客网 时间:2024/06/08 14:05

消防局的建立
本题地址: http://www.luogu.org/problem/show?pid=2279

题目描述

2020年,人类在火星上建立了一个庞大的基地群,总共有n个基地。起初为了节约材料,人类只修建了n-1条道路来连接这些基地,并且每两个基地都能够通过道路到达,所以所有的基地形成了一个巨大的树状结构。如果基地A到基地B至少要经过d条道路的话,我们称基地A到基地B的距离为d。
由于火星上非常干燥,经常引发火灾,人类决定在火星上修建若干个消防局。消防局只能修建在基地里,每个消防局有能力扑灭与它距离不超过2的基地的火灾。
你的任务是计算至少要修建多少个消防局才能够确保火星上所有的基地在发生火灾时,消防队有能力及时扑灭火灾。

输入输出格式

输入格式:
输入文件名为input.txt。
输入文件的第一行为n (n<=1000),表示火星上基地的数目。接下来的n-1行每行有一个正整数,其中文件第i行的正整数为a[i],表示从编号为i的基地到编号为a[i]的基地之间有一条道路,为了更加简洁的描述树状结构的基地群,有a[i]< i。

输出格式:
输出文件名为output.txt
输出文件仅有一个正整数,表示至少要设立多少个消防局才有能力及时扑灭任何基地发生的火灾。

输入输出样例
输入样例#1:
6
1
2
3
4
5
输出样例#1:
2

题解

这是一道蛋疼的树形DP,树形DP一般状态表示比较复杂;在题解帮助下过掉;
%大神Crazyxx http://202.101.104.45:8888/space/show?uid=6076
状态:
dp[i][0]:选自己
dp[i][1]:选了至少一个儿子
dp[i][2]:选了至少一个孙子
———————————–这三种是覆盖了自己的
dp[i][3]: 儿子孙子全部覆盖
dp[i][4]:孙子全部覆盖
———————————–这两种并没有覆盖自己
建议画一棵深度为3的完全二叉树直观观察状态 否则转移方程比较难懂

初始转移方程:
dp[i][0] = 1+Σmin(dp[j][0…4]);
要使选了根节点之后合法(整棵子树包括根节点被覆盖)必须使儿子的孙子全部覆盖 0~4状态满足
dp[i][1] = min( dp[k][0] + Σ(j != k)min(dp[j][0…3]) );
要使选了一个儿子之后合法 由于儿子只可以覆盖到兄弟 所以孙子一定要全部被覆盖 即儿子的儿子一定覆盖 0~3满足
dp[i][2] = min( dp[k][1] + Σ(j != k)min(dp[j][0…2]) );
使选了一个孙子之后合法 由于孙子最多只能覆盖到当前节点 所以儿子一定全部覆盖 即所有儿子本身要被覆盖 0~2满足
dp[i][3] = Σdp[j][0…2];
要使儿子及孙子全部被覆盖 即儿子本身要被覆盖 0~2满足
dp[i][4] = Σdp[j][0…3];
要使孙子全部被覆盖 即儿子的儿子要全部被覆盖 0~3满足
::注意每种状态由儿子转移过来所以根的情况 要转化成对于儿子来说的情况

然后改进状态 因为每种转移方程至少有三种可能最后取其中较小的 故时间效率较低 令dp[i][k]表示min(dp[i][0],dp[i][1]….dp[i][k])且k>=2 因为上述转移方程最少都是0~2状态
那么转移方程就大幅度化简了:
dp[i][0] = 1+Σdp[j][4];
直接由上面变形而来
dp[i][1] = dp[i][4] + min(dp[k][0]-dp[k][3]);
选一个儿子 需保证所有孙子被覆盖 即 dp[i][4] 然后要选出一个儿子 将他从0~3状态变为选了自己(由于dp[i][4]中他是3状态所以要减去一个dp[k][3]) 取这个差值最小的儿子
dp[i][2] = dp[i][3] + min(dp[k][1]-dp[k][2]);
选一个孙子 与上面类似 要保证所有儿子都被覆盖 即dp[i][3] 再将一个儿子从0~2状态变为0~1状态以保证覆盖他父节点
dp[i][3] = Σdp[j][2];
保证所有儿子被覆盖 儿子的0~2状态均符合条件
dp[i][4] = Σdp[j][3];
保证所有儿子的儿子被覆盖 儿子的0~3状态均符合条件

别问我为什么dp[i][1]和dp[1][2]用到后面的状态 因为你只需要在过程中记下那一坨min的值 把3,4处理完后再算1,2
另外由于数据特殊性 编号大的节点一定是编号小的节点的后代 所以递推顺序直接到着推就好了


代码:(神犇的代码稍作改动)

#include<iostream>#include<algorithm>#include<cstdio>using namespace std;const int maxn=1000+10;const int INF=(1<<30);bool G[maxn][maxn];int dp[maxn][5];int main(){    int n;cin>>n;    for(int i=2,tmp;i<=n;i++)    {        scanf("%d",&tmp);        G[tmp][i]=1;    }    for(int i = n ; i>=1 ; i--)    {        int x1=INF,x2=INF;        dp[ i ][ 0 ]=1;        for(int j=1;j<=n;j++)          if(G[ i ][ j ])          {            dp[ i ][ 0 ]+=dp[ j ][ 4 ];            dp[ i ][ 3 ]+=dp[ j ][ 2 ];            dp[ i ][ 4 ]+=dp[ j ][ 3 ];            x1=min(x1,dp[ j ][ 0] -dp[ j ][ 3 ]);            x2=min(x1,dp[ j ][ 1 ]-dp[ j ][ 2 ]);          }        dp[ i ][ 1 ]=dp[ i ][ 4 ]+x1;        dp[ i ][ 2 ]=min(dp[ i ][ 3 ]+x2,min(dp[ i ][ 0 ],dp[ i ][ 1 ]));        dp[ i ][ 3 ]=min(dp[ i ][ 2 ],dp[ i ][ 3 ]);        dp[ i ][ 4 ]=min(dp[ i ][ 3 ],dp[ i ][ 4 ]);    }    printf("%d",dp[ 1 ][ 2 ]);    return 0;}

觉得做完之后对于树形DP有了新的认识 = =涨姿势了

2 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 驾照过期了丢失怎么办 驾照驾照丢了怎么办 泰国驾照丢了怎么办 昆明爱心卡遗失怎么办 换异地户口驾照怎么办 驾照罚款5年没交怎么办 日本签证被拒怎么办 在杭州怎么办日本签证 去日本访友签证怎么办 重庆去日本签证怎么办 申根签证澳洲怎么办 甲方不退保证金怎么办 押金不给退怎么办 在夏门办加急居住证怎么办 学车证明丢失怎么办? 哈尔滨空挂户口怎么办 厦大学生怎么办厦门居住证 厦门网上办理居住证怎么办 宝宝风热咳嗽怎么办 小孩感冒后鼻炎怎么办 小孩感冒引起鼻炎怎么办 婴幼儿喉咙有痰怎么办 驾校的居住证明怎么办 去韩国留学护照怎么办 去韩国签证怎么办d4 出国留学拒签怎么办 高中毕业考不上大学怎么办 深圳怎么办客运营运证 深圳龙岗怎么办居住证 想去日本怎么办签证 孩子没有出生证怎么办通行证 去日本旅游怎么办签证 去美国旅游签证怎么办 我想去泰国怎么办签证 怎么办去泰国的签证 去韩国工作怎么办签证 去越南打工怎么办签证 越南到中国签证怎么办 去越南工作签证怎么办 马来西亚留学签证过期后怎么办 日本留学存款不够怎么办