学习笔记:树形DP

引入

顾名思义,在树上进行动态规划,所以首先要满足,是一棵树,然后观察是否满足DP的要求。我们来具体情况具体分析。

例题

题目大意:在一棵树上安插士兵,在节点上的一个士兵能看到所有与它相连的边,求最少安排的士兵能看到所有的路。

满足是一棵树,而是求最小值。我们考虑用树形DP,对于每一个节点,只有2种状态,放与不放,那么我们用二维存储dp[i][j]表示i节点的状态1或0表示总的士兵数。
状态转移:
若该节点不放,那么它的子节点必须放。
若该节点放,那么它的子节点可放可不放,取最小值即可。
由于是一棵树,根节点没有父节点,所以可以从根开始向叶子节点遍历,回溯回来的到根节点即是答案。

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n,dp[N][2];//只有2种状态  
bool croot[N];
vector<int>a[N];//向量存储邻接表 

void dfs(int root)
{
	dp[root][0]=0;//不放,没士兵 
	dp[root][1]=1;//放,有一个士兵 
	for(int i=0;i<a[root].size();i++)//子节点 
	{
		int y=a[root][i];
		dfs(y);
		dp[root][0]+=dp[y][1]; //父节点不放,子节点必须放 
		dp[root][1]+=min(dp[y][0],dp[y][1]);//父节点放,子节点可放可不放 
	}
}

int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		a[i].clear();
	}	
	memset(croot,1,sizeof(croot));
	int x,y,m;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&x,&m);
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&y);
			a[x].push_back(y);//只从根向下找 
			croot[y]=0; //找根,看是否有入度 
		}
	}
	int root;
	for(int i=0;i<n;i++)
		if(croot[i]==1)
		{
			root=i;//找到了根 
			break;
		}
	
	dfs(root);//遍历树 
	cout<<min(dp[root][1],dp[root][0]);//输出放还是不放 
	return 0;
}

送一道类似的题目luoguP1352
只需要将要参加时,该节点快乐为0,不参加快乐为a[i]

深入

传送门P2015

因为是一个二叉树,我们可以用l和r数组来存储左右儿子,
我们先把边上的苹果转到节点上,
那么状态转移就是左二子保留的节点数+右二子保留的节点数+此节点苹果
其中保留多少就通过枚举,然后不断更新答案,
如果有节点已经搜过,即dp值被改过,直接return,即记忆化搜索

#include<bits/stdc++.h> 
using namespace std; 
 
int n,q,l[105],r[105],a[105]; 
int dp[105][105];
int Map[105][105]; 
 
void MakeTree(int v)    
{ 
	for(int i=1;i<=n;i++)    //建左子树  
	    if(Map[v][i]>=0) 
	    { 
	      	l[v]=i; 
	      	a[i]=Map[v][i];  //把边上苹果转移到点i上 
	     	Map[v][i]=-1;    //边上没有苹果了  
	      	Map[i][v]=-1;   
	     	MakeTree(i); 
	     	break; 
	    } 
	for(int i=1;i<=n;i++)    //建右子树  
	    if(Map[v][i]>=0) 
	    { 
	     	r[v]=i; 
	     	a[i]=Map[v][i];
	     	Map[v][i]=-1;
	     	Map[i][v]=-1;
	     	MakeTree(i); 
	     	break; 
	    } 
} 
 
int DP(int i,int j)                        //树型DP,记忆化搜索  
{ 
	if(j==0) return 0; 
	if((l[i]==0)&&(r[i]==0)) return a[i];  //如果i是叶子节点  
	if(dp[i][j]>0) return dp[i][j];          //如果f[i][j]已经计算直接返回值 
	    for(int k=0;k<=j-1;k++)  
	      	dp[i][j]=max(dp[i][j],DP(l[i],k)+DP(r[i],j-k-1)+a[i]); 
	return dp[i][j];  
} 
 
 
int main() 
{ 
	int x,y,z; 
	scanf("%d",&n,&q);
	q++; 
	memset(Map,-1,sizeof(Map));   
	for(int i=1;i<=n-1;i++) 
	{ 
	    scanf("%d%d%d",&x,&y,&z);
	    Map[x][y]=z; 
	    Map[y][x]=z; 
	} 
	MakeTree(1);              //以1为根建立二叉树  
	cout<<DP(1,q)<<endl; 
	return 0; 
} 

qwq

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值