[HNOI]2003 消防局的建立

7 篇文章 0 订阅
3 篇文章 0 订阅

题目描述
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] 再将一个儿子从02状态变为01状态以保证覆盖他父节点
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;
}

还有一种贪心做法,求最小k覆盖:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000+5;
int dep[maxn], a[maxn],o[maxn],n, fa[maxn];

bool cmp(int i,int j) {
	return dep[i] > dep[j];
}
int main() {
	scanf("%d", &n);
	dep[1] = 1; a[1] = 1; o[1] = n ,fa[1] = 1;
	for(int i = 2; i <= n; ++i) {
		int u;
		scanf("%d", &u);
		dep[i] = dep[u]+1;
		fa[i] = u;
		a[i] = i;
		o[i] = n;
	}
	sort(a+1,a+1+n,cmp);
	int ans = 0;
	for(int i = 1; i <= n; ++i) {
		int v = a[i];
		o[v] = min(o[v],min(o[fa[v]]+1, o[fa[fa[v]]]+2));
		if(o[v] > 2) {
			ans++;
			o[fa[fa[v]]] = 0;
			o[fa[v]] = min(o[fa[v]], 1);
			o[v] = 2;
			int u = fa[fa[v]];
			o[fa[u]] = min(o[fa[u]], 1);
			o[fa[fa[u]]] = min(o[fa[fa[u]]],2);
		}
	}
	cout << ans << endl;
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值