洛谷 P2279 [HNOI2003]消防局的设立

题意

给出一个树,一个消防站能够覆盖与他距离小于2的点。

求覆盖整个树需要多少个消防站。

分析

有贪心思路和动规思路,这篇题解使用动规。

所以按照树形DP的常规套路,我们把一棵子树的状态作为一个划分状态的标准。但是一个子树可能还可以向外覆盖点,也有可能有几个点没覆盖。

由于覆盖半径是2,对于一棵子树划分5种状态:

\(f_{i,0}\)向上覆盖2个。

\(f_{i,1}\)向上覆盖1个。

\(f_{i,2}\)正好覆盖完子树。

\(f_{i,3}\)覆盖完儿子。

\(f_{i,4}\)覆盖完孙子。

需要的消防局数目。


\(f_{i,0}\)选了自己,所以孙子会被覆盖到。要求曾孙全部被覆盖,儿子0-4的状态都满足。

\[f_{i,0}=1+\min(f_{i.child,[0,4]}) \]

\(f_{i,1}\)选了儿子中的一个节点,所以其他儿子也会被覆盖到。但是其他儿子的儿子 无法被覆盖到。所以在转移时,随机选取一个点放消防站,并要求其他点的儿子被覆盖到。

\[f_{i,1}=\min(\min (f_{i.child,[0,3]})_{i.child!=k}+f_{k,0} )_{k\in \{i.child\}} \]

同理有

\[f_{i,2}=\min(\min (f_{i.child,[0,2]})_{i.child!=k}+f_{k,1} )_{k\in \{i.child\}} \]

要让根节点的儿子都被覆盖,就是让它们本身都覆盖(好zz啊)

\[f_{i,3}=\sum(f_{i.child,[0,2]}) \]

同理有

\[f_{i,4}=\sum(f_{i.child,[0,3]}) \]

加速

取[0,2]的最小值,就是覆盖自身时的答案。

对于状态0,[0,4]中显然4最优

\[f_{i,0}=1+\min(f_{i.child,4}) \]

同理有

\[f_{i,3}=\sum(f_{i.child,2}) \]

\[f_{i,4}=\sum(f_{i.child,3}) \]

此时0,3和4的状态处理完成,可以利用它们优化1和2的转移。

状态1,可以让所有孙子被覆盖,然后取\(f_{i.child,0}-f_{i.child,3}\)的最小值。这个式子相当于覆盖与i相邻的点需要的消防站数目。

所以有

\[f_{i,1}=\min (f_{i.child,0}-f_{i.child,3})+f_{i,4} \]

同理有

\[f_{i,2}=\min (f_{i.child,2}-f_{i.child,1})+f_{i,3} \]

最后,你会发现还是贪心好用

(

代码

别看了,很丑,还是抄的。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=1005;
int n,ne,head[MAXN],cnt,temp,temp1,temp2;
int f[MAXN][5];
bool tree[MAXN][MAXN];

int main()
{
	scanf("%d",&n);
	
	for(int i=2;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		tree[x][i]=1;
	}
	for(int i=n;i>=1;i--)
	{
		f[i][0]=1;
		temp1=1919,temp2=1919;
		for(int p=1;p<=n;p++)
		{
			if(tree[i][p])
			{
				f[i][3]+=f[p][2];
				f[i][4]+=f[p][3];
				f[i][0]+=f[p][4];
				temp1=min(temp1,f[p][0]-f[p][3]);
				temp2=min(temp2,f[p][1]-f[p][2]);
			}
		}
		f[i][1]=f[i][4]+temp1;
		f[i][2]=min(f[i][3]+temp2,min(f[i][0],f[i][1]));
		f[i][3]=min(f[i][2],f[i][3]);
		f[i][4]=min(f[i][4],f[i][3]);;
	}
	printf("%d",f[1][2]);
	return 0;
}
posted @ 2019-08-18 13:30  _wkjzyc  阅读(104)  评论(0编辑  收藏  举报