1579 例题5 皇宫看守(LOJ10157 LUOGU2458 提高+/省选-) 树形DP 点看边29分 点看点100分

总目录

在线测评地址(ybt)

在线测评地址(LOJ)

在线测评地址(LUOGU)

1.点看边29分

树形结构!!!

因为是一棵树,所以对于每个节点,我们都把它当成根节点处理→树形dp!!!

注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被了望到。

定义状态dp[u][0/1]表示u这个节点不放/放士兵

根据题意,如果当前节点不放置士兵,那么它的子节点必须全部放置士兵(想到难),因为要满足士兵可以看到所有的边,所以

dp[u][0]+=dp[to][1]

其中to是u的子节点

如果当前节点放置士兵,它的子节点选不选已经不重要了(因为树形dp自下而上,上面的节点不需要考虑),所以

dp[u][1]+=min(dp[to][0],dp[to][1])   (极难想到)

样例模拟如下:

初始化
f[1][0]=0,f[2][0]=0,f[3][0]=0,f[4][0]=0,f[5][0]=0,f[6][0]=0
f[1][1]=30,f[2][1]=16,f[3][1]=5,f[4][1]=4,f[5][1]=11,f[6][1]=5


f[2][0]+=f[6][1],即f[2][0]=5
f[2][1]+=min(f[6][1],f[6][0]),即f[2][1]=16+min(5,0)=16
f[2][0]+=f[5][1],即f[2][0]=16
f[2][1]+=min(f[5][1],f[5][0]),即f[2][1]=16+min(11,0)=16

f[1][0]+=f[2][1],即f[1][0]=16
f[1][1]+=min(f[2][1],f[2][0]),即f[1][1]=30+min(16,5)=35
f[1][0]+=f[3][1],即f[1][0]=21
f[1][1]+=min(f[3][1],f[3][0]),即f[3][1]=35+min(5,0)=35
f[1][0]+=f[4][1],即f[1][0]=25
f[1][1]+=min(f[4][1],f[4][0]),即f[1][1]=35+min(4,0)=35

min(f[1][1],f[1][0])=min(25,35)=25


ybt

未通过

测试点结果内存时间
测试点1答案正确620KB2MS
测试点2答案正确616KB2MS
测试点3答案错误612KB1MS
测试点4答案错误768KB2MS
测试点5答案错误656KB2MS
测试点6答案错误636KB2MS
测试点7答案错误620KB1MS

LOJ 

29分代码如下:

#include <bits/stdc++.h>
#define maxn 1510
using namespace std;
struct node{
	int to,next;
}e[maxn<<1];
int head[maxn],tot;
int f[maxn][2],n;
void add(int u,int v){
	tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;
}
void init(){
	int i,k,u,v,w,j;
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%d%d%d",&u,&w,&k);
		f[u][1]=w;
		for(j=1;j<=k;j++){
			scanf("%d",&v);
			add(u,v),add(v,u);
		}
	}
}
void dfs(int u,int fa){
	int v,i,j;
	for(i=head[u];i;i=e[i].next){
		v=e[i].to;
		if(v==fa)continue;
		dfs(v,u);
		f[u][0]+=f[v][1];
		f[u][1]+=min(f[v][1],f[v][0]);
	}
}
int main(){
	init();
	dfs(1,-1);
	printf("%d\n",min(f[1][0],f[1][1]));
	return 0;
} 

该题与 战略游戏 区别在于:

前者看点,后者看边;

战略游戏相连节点最多是(有值 空 有值) 现在是 (有值 空 空 有值)。

2.点看点100分

我们发现,要使所有点最终全部被覆盖,那么无非有3种状态:

(以下全部都是对于要覆盖任意一个以x为根的子树来说的)
(其中我们设y节点为x的儿子,fa为x的父亲)
1.x节点被自己覆盖,即选择x点来覆盖x点

2.x节点被儿子y覆盖,即选择y点来覆盖x点

3.x节点被父亲fa覆盖,即选择fa点来覆盖x点

借此三种状态,我们可以设f[x][0/1/2]为让以x为根的子树中的节点全部被覆盖,且x点的被覆盖情况为1/2/3时的最小代价

为了方便,我们不妨设这三种情况分别为:

1.f[x][0]---对应上面的1

2.f[x][1]---对应上面的2

3.f[x][2]---对应上面的3

既然是DP,总是有转移方程的,我们想一下dp方程要如何设计

设计状态转移方程:

(1):对应上面的1.

f[x][0]=∑ min(f[y][0],f[y][1],f[y][2]) + val[x]

其中val[x]是选择x点的代价

我们很容易想到,在节点x被选择之后,我们就可以无拘无束了(蛤?),也就是说对于x儿子节点y的状态可以不去考虑,因为x节点被选择之后y节点无论如何也会被覆盖到,所以我们在儿子y的所有状态里取min,累加起来就行了

(2):对应上面的3(先讲3,因为2比较难以理解,放到了后面)

f[x][2]=∑ min(f[y][0],f[y][1])

为什么3情况对应的转移方程要这样写呢?

我们不妨这样理解,对于x节点我们让它的父亲节点fa覆盖它,那么根据我们的状态设计,此时必须要满足以x的儿子y为根的子树之中所有点已经被覆盖

那么这时就转化为一个子问题要让y子树满足条件只有两种决策:要么y被y的儿子覆盖,要么被y自己覆盖(即选择y节点),只需要在y的这两种状态取min累加就可以了

(3):对应上面的2(DuangDuangDuang 敲黑板划重点啦)

f[x][1]=∑ min(f[y][0],f[y][1]),如果选择的全部都是f[y][1],要再加上min(f[y][0]-f[y][1])

这又是什么意思呢?真是让人摸不着头发,,,质壁分离(逃

到了这里,我们就要回顾一下我们设计的dp状态了:

** 设f[x][0/1/2]为让以x为根的子树中的节点全部被覆盖,且x点的被覆盖情况为1/2/3时的最小代价**

先提示一下,如果你理解了下面,那么本题是很简单的。。如果你没理解,就返回到这里再看一遍吧,我就在这里等着你

咳咳。。说正经的。。(逃

对于此时的状态,f[x][1]代表对于节点x让x被自己的儿子覆盖,那么和分析(2)一样,都要先满足此时以y的子树已经满足了条件,才能进行转移,这就是前面那部分:∑ min(f[y][0],f[y][1])的来历,那么后面那一长串又是怎么回事呢?

我们可以这样理解,此时既然要保证x点是被自己的儿子覆盖的,那么如果此时y子树已经满足了全部被覆盖,但是y此时被覆盖的状态却是通过y节点自己的儿子达到的,那么x就没有被儿子y覆盖到,那么我们不妨推广一下,如果x所有的儿子y所做的决策都不是通过选择y点来满足条件,那么我们就必须要选择x的一个子节点y,其中y满足f[y][0]-f[y][1]最小,并把这个最小的差值累加到f[x][1]中去这样才能使得x点被自己的儿子覆盖,状态f[x][1]也才能合理地得到转移

好了,如果你还是没有太懂这个(3)的设计过程,请你回到之前再仔细看几遍

如果你已经理解了上面,那么恭喜你这个题,你已经A掉了

因为转移方程既然有了,那么我们就只需要最后的答案了

由于题目中没有说这棵树的根节点是哪个,所以你可以默认1就是根,或者开一个数组在加边的时候记录一下每个点的入度,最后没有入度的点就是根(但好像没有区别,毕竟我A掉了)

最后答案即为min(f[root][0],f[root][1])

因为根节点没有父亲,所以不需要考虑它的f[root][2]状态.

ybt

通过

测试点结果内存时间
测试点1答案正确620KB1MS
测试点2答案正确616KB2MS
测试点3答案正确616KB2MS
测试点4答案正确768KB2MS
测试点5答案正确652KB2MS
测试点6答案正确628KB2MS
测试点7答案正确620KB2MS

LOJ

 

LUOGU

AC代码

#include <bits/stdc++.h>
#define maxn 1510
using namespace std;
struct node{
	int to,next;
}e[maxn<<1];
int head[maxn],tot;
int n,f[maxn][3];
void add(int u,int v){
	tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;
}
void init(){
	int u,v,i,k,j,val;
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%d%d%d",&u,&val,&k);
		f[u][0]=val;
		for(j=1;j<=k;j++){
			scanf("%d",&v);
			add(u,v),add(v,u);
		}
	}
}
void dfs(int u,int fa){
	int i,v,sum=0,mn=2000000000;
	for(i=head[u];i;i=e[i].next){
		v=e[i].to;
		if(v==fa)continue;
		dfs(v,u);
		f[u][0]+=min(min(f[v][0],f[v][1]),f[v][2]);
		f[u][2]+=min(f[v][0],f[v][1]);
		if(f[v][0]<f[v][1])sum++,f[u][1]+=f[v][0];
		else f[u][1]+=f[v][1],mn=min(mn,f[v][0]-f[v][1]);
	}
	if(!sum)f[u][1]+=mn;
}
int main(){
	init();
	dfs(1,-1);
	printf("%d\n",min(f[1][0],f[1][1]));
	return 0;
}

该题习得什么?

深搜中,局部变量的作用范围,详见代码中的sum,mn变量。

对动归中的放,与不放,稍微有了点触动。

动归中的转移方程,重在转移二字,转移体现了承上启下。

该题(点看点动归)要与 战略游戏(点看边动归) 对称着做。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值