bzoj1217(洛谷P2279)消防局的设立(dp或贪心)

本文通过一个具体的题目介绍了树形DP与贪心算法的应用。首先使用贪心算法解决问题,然后介绍了更为复杂的树形DP解决方案。两种方法都考虑了在树状结构中设置消防站以覆盖尽可能多的节点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.前言

我现在很气。

气的理由和我那篇讲cqoi2017树形dp的博客差不多.....推dp方程推到想吐最后发现是个贪心。

我现在真的要因为树形dp怀疑人生了。

2.贪心做法

首先dfs求出每一个节点的深度,然后深度从大到小排序(或者保存每个深度的所有节点),对于每一个不能被消防局覆盖到的节点,在它的爷爷节点上设置一个消防局最优,为什么呢?我不会理性的证明,只会感性的,感性的证明就三个字:显然嘛!

设置了以后,更新所有和爷爷节点距离为2以内的点的状态,注意!不要瞎剪枝(反正2003年的题数据范围很小)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<iomanip>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
int read(){//读入优化是一种态度
	int w=1,q=0;char ch=' ';
	while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
	if(ch=='-')w=-1,ch=getchar();
	while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
	return w*q;
}
int n,tot,dee,ans;
vector<int>cen[1005];
int ne[2005],h[1005],go[2005],fa[1005];//少用vector是一种态度
bool vis[1005];
void add(int x,int y){go[++tot]=y;ne[tot]=h[x];h[x]=tot;}
void dfs(int x,int las,int dep){
	dee=max(dee,dep);fa[x]=las;
	cen[dep].push_back(x);
	for(int i=h[x];i;i=ne[i])
		if(go[i]!=las)dfs(go[i],x,dep+1);
}
void bfs(int x,int bs,int las){
	vis[x]=1;if(bs==2)return;
	for(int i=h[x];i;i=ne[i])
		//if(!vis[go[i]])这句话是万恶之源(WA了两次)
		bfs(go[i],bs+1,x);
}
int main()
{
	int i,j,x;
	n=read();
	for(i=2;i<=n;i++){j=read();add(i,j);add(j,i);}
	dfs(1,0,1);
	for(i=dee;i>=1;i--)
		for(j=0;j<cen[i].size();j++){
			x=cen[i][j];
			if(vis[x])continue;
			ans++;
			if(fa[fa[x]])bfs(fa[fa[x]],0,0);
			else if(fa[x])bfs(fa[x],0,0);
			else bfs(x,0,0);
		}
	printf("%d",ans);
   	return 0;
} 

3.树形dp做法

这个就非常恶心了.....而且也借鉴了贪心思想,不过比贪心程序跑的快些。当遇见看不懂的地方的时候,请读一读下面这句话:

前提:依据贪心思想,允许范围内,两个消防站距离越远,利用的越好,是更优决策。

我是看这这位大神的博客:http://blog.youkuaiyun.com/qwsin/article/details/50954698

这样的:

f[i][0]表示在i处建立消防站。

f[i][1]表示在至少一个i的儿子处建立了消防站。

f[i][2]表示在至少一个i的孙子处建立了消防站。

-----那么以上三种情况i肯定是可以被消防站救火的------

f[i][3]表示i的所有儿子和孙子都可以被消防站救火(不一定消防站建在那里)

f[i][4]表示i的所有孙子都可以被救火。

-----以上情况i不一定可以被救火-----

对于f[i][0],其儿子和孙子都可以被覆盖(也就是能被救火,但不一定在上面有消防局),但是儿子的孙子就不行了,所以儿子的孙子必须保证可以被覆盖,所以:

f[i][0]=1+Σf[j][4];

对于f[i][1],我们选择了一个儿子后,所有儿子都可以被覆盖,但是不是所有孙子都会被覆盖。我们先让所有孙子被覆盖,然后选择一个点修改,现在重新读一读开头的前提,那么在孙子被覆盖,也就是f[i][4]情况里,所有儿子的状态都是3,所以要修改一个儿子的状态从3变成0:

f[i][1]=f[i][4]+min(f[son[i]][0]-f[son[i]][3]);

对于f[i][2],我们选择一个孙子后,不是所有儿子和孙子都可以被覆盖,那么再读一读前提,我们要在儿子和孙子都覆盖的情况下改变某个儿子的状态(从2改到1)

f[i][2]=f[i][3]+min(f[son[i]][1]-f[son[i]][2]);

对于f[i][3],每个儿子最远可以救火的地方,也就是在2状态下最优,读一读前提吧。

f[i][3]=Σf[son[i]][2];

对于f[i][4],读一读前提就知道了。

f[i][4]=Σf[son[i]][3];

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<climits>
#include<iomanip>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
int read(){
	int w=1,q=0;char ch=' ';
	while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
	if(ch=='-')w=-1,ch=getchar();
	while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
	return w*q;
}
int n,tot,inf=INT_MAX;
int go[2005],ne[2005],h[1005];
int f[1005][6];
void add(int x,int y){go[++tot]=y;ne[tot]=h[x];h[x]=tot;}
void dfs(int x,int las){
	int mx1=inf,mx2=inf;
	int i,bj=0;
	for(i=h[x];i;i=ne[i])
		if(go[i]!=las){
			dfs(go[i],x);
			f[x][0]+=f[go[i]][4];f[x][3]+=f[go[i]][2];f[x][4]+=f[go[i]][3];
			mx1=min(mx1,f[go[i]][0]-f[go[i]][3]);
			mx2=min(mx2,f[go[i]][1]-f[go[i]][2]);bj=1;
		}
	if(!bj){f[x][0]=f[x][1]=f[x][2]=1;f[x][3]=f[x][4]=0;}
	f[x][0]++;f[x][1]=f[x][4]+mx1;f[x][2]=f[x][3]+mx2;
	for(i=1;i<=4;i++)f[x][i]=min(f[x][i],f[x][i-1]);
}
int main()
{
	int i,j;
	n=read();
	for(i=2;i<=n;i++){j=read();add(i,j);add(j,i);}
	dfs(1,0);
	printf("%d",f[1][2]);
   	return 0;
} 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值