DP练习9(经典树形动规)

题目背景

传送门

Bob 喜欢玩电脑游戏,特别是战略游戏。但是他经常无法找到快速玩过游戏的办法。现在他有个问题。

题目描述

他要建立一个古城堡,城堡中的路形成一棵无根树.他要在这棵树的结点上放置最少数目的士兵,使得这些士兵能了望到所有的路。
注意,某个士兵在一个结点上时,与该结点相连的所有边将都可以被了望到.
请你编一程序,给定一树,帮Bob计算出他需要放置最少的士兵。

输入格式

第一行一个整数 n,表示树中结点的数目。

第二行至第n+1行,每行描述每个结点信息,依次为:一个整数i,代表该结点标号,一个自然数k,代表后面有k条无向边与结点i相连.接下来k个整数,分别是每条边的另一个结点标号 r 1 , r 2 , ⋯   , r k {r_1,r_2,\cdots,r_k} r1,r2,,rk,表示i与这些点间各有一条无向边相连。

对于一个n个结点的树,结点标号在0到n-1之间,在输入数据中每条边只出现一次.保证输入是一棵树.

输出格式

输出文件仅包含一个整数,为所求的最少的士兵数目。

输入输出样例
输入 #1
4
0 1 1
1 2 2 3
2 0
3 0
输出 #1
1
说明/提示
数据规模与约定
对于全部的测试点,保证 1 ≤ n ≤ 1500 {1 \leq n \leq 1500} 1n1500

解题思路

这是一个树形动规的基础题,与没有上司的舞会解法一样,但本题是求最小值所以有些小的变动.以下是代码步骤:

1.预处理+找根

首先我们需要一个结构体来储存节点的编号儿子个数和儿子,而由于这是一棵无根树,所以我们需要对做儿子的节点打上标记,最后扫描一遍,扫到没打标记的节点便是根节点.

2.推导DP的状态转移方程和DP答案

我们可以和没有上司的舞会一样用 f [ i ] [ 0 ∣ 1 ] {f[i][0|1]} f[i][01]表示以i为根结点的子树在i是否驻兵的情况下(0表示不驻,1表示驻)驻兵总数的最小值.首先必须将 f [ i ] [ 0 ] {f[i][0]} f[i][0]初始化为0, f [ i ] [ 1 ] {f[i][1]} f[i][1]初始化为1,然后我们同理也可以推出当i节点不驻兵时,它的子节点就一定得驻兵,如果i节点驻兵则加上其子节点是否驻兵的最小值.所以,状态转移方程就出来了:
f [ i ] [ 0 ] = f [ i ] [ 0 ] + f [ s o n [ i ] ] [ 1 ] {f[i][0]=f[i][0]+f[son[i]][1]} f[i][0]=f[i][0]+f[son[i]][1];
f [ i ] [ 1 ] = f [ i ] [ 1 ] + m i n ( f [ s o n [ i ] ] [ 0 ] , f [ s o n [ i ] ] [ 1 ] ) {f[i][1]=f[i][1]+min(f[son[i]][0],f[son[i]][1])} f[i][1]=f[i][1]+min(f[son[i]][0],f[son[i]][1]);
a n s = m i n ( f [ r o o t ] [ 0 ] , f [ r o o t ] [ 1 ] ) {ans=min(f[root][0],f[root][1])} ans=min(f[root][0],f[root][1]).

3.Dfs+树上DP

解决了DP之后就是实现问题了,这里要注意的是如果一个节点已经没有子节点,那么在将其DP状态预处理后直接返回,若有则往子节点遍历并累加ans,最后注意状态转移方程不要打错了即可.

完整代码

#include
#define N 1505
#define in read()
using namespace std;

int n,ans,f[N][2],root;
struct{
int poi,num,son[N];}t[N];
bool rd[N];

inline int in{
	int i=0;char ch;
	while(!isdigit(ch)){ch=getchar();}
	while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch-'0');ch=getchar();}
	return i;
}

inline void Init()//预处理找根
{
	
	n=in;
	for(int i=0;i<n;i++)
	{
		t[i].poi=in,t[t[i].poi].num=in;
		for(int j=1;j<=t[t[i].poi].num;j++)
		{
			t[t[i].poi].son[j]=in;
			if(!rd[t[t[i].poi].son[j]])rd[t[t[i].poi].son[j]]=1;
		}
	}
	root=0;
	while(rd[root])root++;
	return;
}

inline void Dfs(int u)//Dfs加DP
{
	f[u][0]=0,f[u][1]=1;
	if(!t[u].num)return;
	for(int i=1;i<=t[u].num;i++)
	{
		int v=t[u].son[i];
		Dfs(v);
		f[u][1]=min(f[u][1]+f[v][0],f[u][1]+f[v][1]);
		f[u][0]=f[u][0]+f[v][1];
	}
	return;
}

int main()
{
	Init();
	Dfs(root);
	ans=min(f[root][0],f[root][1]);//取最小答案.
	printf("%d\n",ans);
	return 0;
}

完结撒花!!!

你可能感兴趣的:(————DP————,树形动态规划)