[洛谷]P2458 [SDOI2006]保安站岗 (#树形dp)

题目描述

五一来临,某地下超市为了便于疏通和指挥密集的人员和车辆,以免造成超市内的混乱和拥挤,准备临时从外单位调用部分保安来维持交通秩序。

已知整个地下超市的所有通道呈一棵树的形状;某些通道之间可以互相望见。总经理要求所有通道的每个端点(树的顶点)都要有人全天候看守,在不同的通道端点安排保安所需的费用不同。

一个保安一旦站在某个通道的其中一个端点,那么他除了能看守住他所站的那个端点,也能看到这个通道的另一个端点,所以一个保安可能同时能看守住多个端点(树的结点),因此没有必要在每个通道的端点都安排保安。

编程任务:

请你帮助超市经理策划安排,在能看守全部通道端点的前提下,使得花费的经费最少。

输入格式

第1行 n,表示树中结点的数目。

第2行至第n+1行,每行描述每个通道端点的信息,依次为:该结点标号i(0<i<=n),在该结点安置保安所需的经费k(<=10000),该边的儿子数m,接下来m个数,分别是这个节点的m个儿子的标号r1,r2,...,rm。

对于一个n(0 < n <= 1500)个结点的树,结点标号在1到n之间,且标号不重复。

输出格式

最少的经费。

如右图的输入数据示例

输出数据示例:

输入输出样例

输入 #1复制

6
1 30 3 2 3 4
2 16 2 5 6
3 5 0
4 4 0
5 11 0
6 5 0

输出 #1复制

25

说明/提示

样例说明:在结点2,3,4安置3个保安能看守所有的6个结点,需要的经费最小:25


思路

我感觉和P2899 手机网络是一模一样的题目......相比之下,只需要改2处代码......(双倍经验)对于P2899那道题,我已经写了详细的题解。P2899的题解:这篇文章

首先对于每个节点i,都有如下3种选择:
不选自己,选儿子;
不选自己,选父亲;
我自己选我自己。

令dp[i][0/1/2]为节点i及其节点i的子树中全部被覆盖所需的最小保安数,dp[i][0]表示点i有保安,dp[i][1]为点i没有保安,也就是就是父亲有信号塔,dp[i][2]表示节点i被间接管辖,也就是儿子有保安。

1.dp[i][0]

因为节点i有保安,所以对于i的子节点son,它可以没有保安,也可以有保安,也可以从son的子节点转移过来。因此:

dp[i][0]=∑min(dp[son][1],dp[son][0],dp[son][2])+val[i]

这里+val[i]是因为自己本身放置了一个保安,产生的代价。

2.dp[i][1]

因为节点i没有保安,靠的是节点i的父亲才被覆盖的,所以对于节点i的子节点son是不可能选父亲的。所以dp[i][1]可以从dp[son][0]转移过来(子节点有保安),也可以从dp[son][2]转移过来(子节点的儿子有保安)。

dp[i][1]=∑min(dp[son][0[,dp[son][2])

3.dp[i][2]

因为节点i没有保安,靠它儿子,所以节点i的子节点son也不可能选父亲,必然可以从dp[son][0]转移过来,也可以从dp[son][2]转移过来。

dp[i][2]=∑min(dp[son][0],dp[son][2])

很快会发现,如果的确是这样的话,如果dp[i][2]从dp[son][2]转移过来,也就是若恒有dp[son][2] ≤ dp[son][0],就意味着节点i的所有子节点都没有保安!那不就凉了)怎么办?

所以我们要设立一个反悔机制我只需要一个儿子选。只需要用p来记录每一次dp[son][0]-min(dp[son][2],dp[son][0]),这样就保证,就算恒有dp[son][2] ≤ dp[son][0],也一定有一个是儿子选了的,如果不选dp[son][2],最后p的状态也为0。若不信,我们做个推导:

记p=min(p,dp[son][0]-min(dp[son][0],dp[son][2]))

dp[i][2]=min(dp[son][0],dp[son][2])+p

=min(dp[son][0],dp[son][2])+dp[son][0]-min(dp[son][0],dp[son][2])

若dp[son][2] ≤ dp[son][0]恒成立(注意我说的是恒成立,意思是说dp[i][2]全是从dp[son][2]转移过来)

原式=dp[son][2]+dp[son][0]-dp[son][2]

=dp[son][0]

也就是把dp[son][2]强制转换成了dp[son][0]!

若dp[son][2]>dp[son][0]

原式=dp[son][0]+dp[son][0]-dp[son][0]

=dp[son][0]

也就是说并不影响dp[son][0]的正常取值!

最后答案为min(dp[root][0],dp[root][2])。

#include <stdio.h>
#include <iostream>
#define inf 2e9+7
#define N 2001
using namespace std;
int n,m,s,dp[N][3],head[N],cnt,val[N];
struct node
{
	int nxt,to;
}e[N<<1];
inline void add(int u,int v)
{
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
void dfs(int u,int fa)
{
	register int i,p(inf);
	dp[u][0]=val[u];
	for(i=head[u];i;i=e[i].nxt)
	{
		int v(e[i].to);
		if(v==fa) continue;
		dfs(v,u);
		dp[u][0]+=min(dp[v][0],min(dp[v][1],dp[v][2]));
		dp[u][1]+=min(dp[v][0],dp[v][2]);
		dp[u][2]+=min(dp[v][0],dp[v][2]);
		p=min(p,dp[v][0]-min(dp[v][2],dp[v][0]));//表示其它儿子的总和
	}
	dp[u][2]+=p;//建立反悔机制 
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register int i,j,k;
	cin>>n;
	for(i=1;i<=n;i++)
	{
		int u,v;
		cin>>u;
		cin>>val[u]>>m;
		while(m--)
		{
			cin>>v;
			add(u,v);
			add(v,u);
		}
	}
	dfs(1,-1);
	cout<<min(dp[1][0],dp[1][2])<<endl;
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值