P2015 binary apple tree (tree DP)

Posted by lobski on Sat, 26 Feb 2022 04:14:55 +0100

Title Link: Binary apple tree - Luogu

Analysis: This is a tree DP topic. It's not too difficult, but it's easy to grasp the details. Let's explain this topic directly below:

Let f[i][j] represent the maximum number of apples that can be retained by retaining j branches in the subtree with I as the root. After knowing the state representation, we can easily write the state transition equation. If we traverse the root node of X, we need to discuss whether to take the left and right subtrees by categories. Of course, this is not very difficult. We can discuss it directly by category, But this is different from ordinary tree DP in that we need to consider the left and right subtrees at the same time, so we need to open a new array in the process of tree DP to record the left and right children of the current node. How to record it? I made a mistake here at the beginning. I'll explain what the mistake is to prevent everyone from entering the pit. The title says that a branch must be bifurcated, so we can make the node of i==h[x] as the left child and vice versa as the right child. Isn't that very good? Then why did it go wrong? In fact, it's because we ignore a problem, that is, we build two-way edges when we build edges, so we also build an edge pointing to its parent node for each point, so there are three in total, so there will be errors here. Therefore, in the correct way, we can make the first node that is not the parent node the left child and the second child that is not the parent node the right child, In this way, this problem is solved, and there is nothing else. The code of this idea is attached below:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=303;
int f[N][N];//f[i][j] represents the maximum number of apples that can be retained by retaining j branches in the subtree with I as the root
int h[N],e[N],ne[N],w[N],idx;
int n,m;
int lson[N],rson[N],lsonw[N],rsonw[N];//lson[i] records the left child node of node i, and lsonw[i] records the edge weight between node i and its left child 
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs(int x,int fa)
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==fa) continue;
		if(!lson[x])//If the left son does not exist, it is equal to the left son
		{
			lson[x]=j;
			lsonw[x]=w[i];
		}
		else
		{
			rson[x]=j;
			rsonw[x]=w[i];
		}
		dfs(j,x);
		for(int k=1;k<=m;k++)
		{
			f[x][k]=max(f[x][k],f[lson[x]][k-1]+lsonw[x]);//Keep only the left subtree 
			f[x][k]=max(f[x][k],f[rson[x]][k-1]+rsonw[x]);//Keep only the right subtree
			if(k>=2)//Both left and right subtrees are reserved 
			for(int t=0;t<=k-2;t++)
				f[x][k]=max(f[x][k],f[lson[x]][t]+lsonw[x]+f[rson[x]][k-t-2]+rsonw[x]);
		}
	}
}
int main()
{
	cin>>n>>m;
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++)
	{
		int u,v,z;
		scanf("%d%d%d",&u,&v,&z);
		add(u,v,z);add(v,u,z);
	}
	dfs(1,-1);
	printf("%d",f[1][m]);
	return 0;
}

Here's another idea that is a little simpler than the above method: that is, we directly consider each child alone rather than both left and right children, which means that we consider the number of branches that each child should keep separately, and the transfer equation is very simple, that is

for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==fa) continue;
		dfs(j,x);
		for(int k=1;k<=m;k++)
			for(int l=0;l<=k-1;l++)
				f[x][k]=max(f[x][k],f[x][k-1-l]+f[j][l]+w[i]);
	}

It's easy for you to understand that it's just to enumerate the number of branches originally reserved by the root node and the number of branches that should be reserved by the current child? Of course, this idea is correct, but there is still a little problem in my program, that is, when we update f[x][k], K is from small to large, while f[x][k-1-l] is from large to small, that is, in the normal DP process, we may use the f array of smaller K updated by the current child when updating the larger K value with the current child, resulting in an error, If you understand this, it's easy to modify. You can solve this problem by copying the f array directly. The following is the correct transfer equation. You can think about it carefully:

for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==fa) continue;
		dfs(j,x);
		for(int k=1;k<=m;k++)
			t[x][k]=f[x][k];
		for(int k=1;k<=m;k++)
			for(int l=0;l<=k-1;l++)
				f[x][k]=max(f[x][k],t[x][k-1-l]+f[j][l]+w[i]);
	}

The complete code is attached below:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=303;
int f[N][N],t[N][N];//f[i][j] represents the maximum number of apples that can be retained by retaining j branches in the subtree with I as the root
int h[N],e[N],ne[N],w[N],idx;
int n,m;
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs(int x,int fa)
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==fa) continue;
		dfs(j,x);
		for(int k=1;k<=m;k++)
			t[x][k]=f[x][k];
		for(int k=1;k<=m;k++)
			for(int l=0;l<=k-1;l++)
				f[x][k]=max(f[x][k],t[x][k-1-l]+f[j][l]+w[i]);
	}
}
int main()
{
	cin>>n>>m;
	memset(h,-1,sizeof h);
	for(int i=1;i<n;i++)
	{
		int u,v,z;
		scanf("%d%d%d",&u,&v,&z);
		add(u,v,z);add(v,u,z);
	}
	dfs(1,-1);
	printf("%d",f[1][m]);
	return 0;
}

Topics: Dynamic Programming tree