[learning notes] tree dynamic programming

Posted by tentaguasu on Fri, 07 Jan 2022 14:34:00 +0100

About tree DP

Tree dynamic programming, as the name suggests, is to do dynamic programming on the data structure of the tree. Because the tree is naturally a recursive data structure, the implementation of tree DP is usually memory search.

Because there are two types of transfer: push and pull, the transfer of natural tree DP also has two orders:

  1. leaf → \rightarrow → root, calculate the child node information of a node to obtain the information of the node, which is a common transfer order;
  2. root → \rightarrow → leaf, this transfer sequence is very rare, but it is not absent. (passerby: This is not nonsense

The time complexity of tree DP. Beginners may make mistakes in analysis. Most common topics are O ( n ) O(n) O(n). Let's analyze:
In most cases, the number of States is the number of nodes n n n. The complexity of each state transition is O ( son i ) O(\text{son}_i) O(soni) (here) son i \text{son}_i soni = node i i i), so the total complexity is O ( ∑ i = 1 n son i ) O(\sum_{i=1}^n \text{son}_i) O(∑ i=1n ∑ soni), i.e O ( n ) O(n) O(n).

The use situation of tree DP is very easy to judge, that is, do DP on the tree. For some problems, we can establish models and solve them with the data structure of trees.

In fact, the tree DP also has a little color of routine questions. In many cases, our state is "set" d p i dp_i dpi + i i i is in the subtree of the root node... ". in order to design the state transition equation, we consider the relationship between the current state of this node and its parent node and its child nodes.

Classic problems & Ex amp les

The classical problems of tree DP include: the center of gravity of the tree, the longest path / longest chain / farthest point pair / diameter of the tree, the center of the tree, the point coverage of the tree, the independent set of the tree, etc. These problems are relatively basic, so we won't talk about them. They are found in many textbooks.

Example 1

One book 5.2 exercise 2 tourism planning

First, clarify our idea: we first require the length of the longest path, and then judge whether each point is on the longest path, that is, whether the longest path passing through this point can reach the length of the global longest path.

Step 1: find the length of the longest path.
This is a classic question. If we take any point as the root and turn the rootless tree into a rooted tree, then one of the longest chains in the tree must be in the subtree with a node as the root and pass through this node. Therefore, the length of the longest chain passing through this node in a subtree with a point as the root must be equal to the sum of the longest chain and the second longest chain starting downward from this point (i.e. not beyond the subtree).
Use separately d 1 i d1_i d1i} and d 2 i d2_i d2i = slave node i i i the length of the longest chain and the second longest chain starting downward, then enumeration i i Child nodes of i v v v. Yes

  • If d 1 v + 1 > d 1 i d1_v+1>d1_i d1v + 1 > d1i, then d 2 i ← d 1 i d2_i \leftarrow d1_i d2i ← d1i ← and d 1 i ← d 1 v + 1 d1_i \leftarrow d1_v + 1 d1i​←d1v​+1;
  • If not satisfied d 1 v + 1 > d 1 i d1_v+1>d1_i d1v + 1 > d1i # but d 1 v + 1 > d 2 i d1_v+1>d2_i d1v + 1 > d2i, then d 2 i ← d 1 v + 1 d2_i \leftarrow d1_v+1 d2i​←d1v​+1.

The length of the longest path is max ⁡ { d 1 i + d 2 i } \max \{d1_i+d2_i\} max{d1i​+d2i​}

Step 2: find the longest path through a point.
The longest path from one point has only two forms: the longest path in the subtree and the longest path not in the subtree.
The longest path in the subtree: just found in step 1, that is d 1 i d1_i d1i​;
Longest path not in subtree: Set u p i up_i upi , indicates the slave point i i i is the longest path starting upward, then it must pass through the point i i Parent node of i f a fa fa. Next, there are two situations:

  1. Go to f a fa In the subtree of fa. Then we need to record d 1 f a d1_{fa} Where did d1fa# it come from. set up c i c_i ci indicates d 1 i d1_i d1i # is from c i c_i ci transferred, if c f a = i c_{fa} = i cfa = i, then the length of the whole path is d 1 i + d 2 f a + 1 d1_i+d2_{fa}+1 d1i + d2fa + 1, otherwise repeated calculation will occur; Otherwise, the length of the whole path is d 1 i + d 1 f a + 1 d1_i+d1_{fa}+1 d1i​+d1fa​+1.
  2. No entry f a fa The subtree of fa, then the path length is d 1 i + u p f a + 1 d1_i+up_{fa}+1 d1i​+upfa​+1.

Take the maximum of the above three possible path lengths.

The idea is ready to come out: twice DFS (or twice DP), the first time d 1 i , d 2 i d1_i,d2_i d1i, d2i} and c i c_i ci, find it the second time u p i up_i upi​.

The purpose of adding this question is to review the solution of the longest path in the tree; The second is to show the memory search without return value type (void); Third, let's tell you that we often use DFS twice in tree DP.

The rest is enough to explain in Code:

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 2e5 + 10;
int n, h[maxn], en;
struct Edge
{
	int u;
	int v;
	int next;
} e[maxn << 1];
int d1[maxn], d2[maxn], c[maxn], up[maxn];

void addedge(int u, int v)
{
	en++;
	e[en].u = u;
	e[en].v = v;
	e[en].next = h[u];
	h[u] = en;
	return;
}

void dfs1(int x, int fa)
{
	for(int i = h[x]; i != 0; i = e[i].next)
	{
		int v = e[i].v;
		if(v == fa)
			continue;
		dfs1(v, x);
		if(d1[v] + 1 > d1[x])
		{
			d2[x] = d1[x];
			d1[x] = d1[v] + 1;
			c[x] = v;
		}
		else
			d2[x] = max(d2[x], d1[v] + 1);
	}
	return;
}

void dfs2(int x, int fa)
{
	up[x] = max(up[fa], c[fa] == x ? d2[fa] : d1[fa]) + 1;
	for(int i = h[x]; i != 0; i = e[i].next)
		if(e[i].v != fa)
			dfs2(e[i].v, x);
	return;
}

int main()
{
	scanf("%d", &n);
	for(int i = 1; i < n; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		u++;
		v++;
        //The point number in the title is 0,1 N-1, in order to facilitate operation and avoid unexpected errors
        //Add 1 to both u and v
		addedge(u, v);
		addedge(v, u);
	}
	dfs1(1, 0);
	dfs2(1, 0);
	int len = 0;
	for(int i = 1; i <= n; i++)
		len = max(len, d1[i] + d2[i]);  //Find the length of the longest path
	for(int i = 1; i <= n; i++)  //Determine which points are on the longest path
		if(d1[i] + d2[i] == len || d1[i] + up[i] == len)
			printf("%d\n", i - 1);  //Note that the number is increased by 1 at the beginning
	return 0;	
}

Example 2

ZJOI2008 Knight

Observation: if a knight hates another knight and we establish an undirected edge between the two knights, the whole picture is a base ring tree forest.
Proof: the whole graph is composed of several connected blocks. Suppose the number of connected block points is m m m. Because every knight has only one most hated knight and is not himself, there will be in this connection block m m m edges. One m m m-node graph, with m m m edges and connected, which is naturally a base ring tree. A base ring tree is a tree with an edge added to form a ring.

Then as long as we make the combat effectiveness of each base ring tree as large as possible, the total combat effectiveness of the base ring tree forest will be the largest.

Take the base ring tree as a ring, and each node on the ring is used as the root node to grow a rooted tree. In each tree, we do the "dance without boss" model (the title is here ), and then do the sequence DP on the ring again. But there is a problem: we can break the ring into a chain. Here we can't break the ring into a chain. Just do a special treatment.

Code: (the code size is a little large)

//C++11
#include<cstdio>
#include<cctype>
#include<vector>
#include<stack>
using namespace std;
const int maxn = 1000010;
int n;
long long a[maxn];
int h[maxn], en;
struct Edge
{
	int u;
	int v;
	int next;
};
Edge e[maxn << 1];

stack<int> stk;
vector<int> cycle;
long long f[maxn][2], g[maxn][2][2], ans;
bool circ[maxn], vis[maxn], found[maxn], ok;

inline long long read()
{
	long long x = 0;
	bool flag = true;
	char ch = getchar();
	while(!isdigit(ch))
	{
		if(ch == '-')
			flag = false;
		ch = getchar();
	}
	while(isdigit(ch))
	{
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	return flag ? x : -x;
}

inline void addedge(int u, int v)
{
	en++;
	e[en].u = u;
	e[en].v = v;
	e[en].next = h[u];
	h[u] = en;
	return;
}

inline int get(int edge)  //Find reverse side number
{
	return edge & 1 ? edge + 1 : edge - 1;
}

void find_circle(int x, int pre)
{
	if(ok) return;  //The ring has been found
	stk.push(x);  //Put the node in the stack
	if(found[x])  //Found to form a ring
	{
		while(!stk.empty() && !circ[stk.top()])  //Pour out the nodes in the stack
		{
			circ[stk.top()] = true;
			cycle.push_back(stk.top());
			stk.pop();
		}
		ok = true;
		return;
	}
	found[x] = true;
	for(int i = h[x]; i != 0; i = e[i].next)
	{
		if(get(i) == pre)
			continue;
        //Note that the reverse edge is used here instead of walking back to the original node
        //Because two knights may hate each other
		int v = e[i].v;
		find_circle(v, i);
	}
	if(!stk.empty())
		stk.pop();   //Pay attention to details
	return;
} 

void dp(int x, int fa)
{
	vis[x] = true;
	f[x][1] = a[x];
	for(int i = h[x]; i != 0; i = e[i].next)
	{
		int v = e[i].v;
		if(v == fa || circ[v])
			continue;
		dp(v, x);
		f[x][0] += max(f[v][0], f[v][1]);
		f[x][1] += f[v][0]; 
	}
	return;
}

inline void dp2()
{
    //In g[x][b1][b2], b1 indicates whether x is selected or not, and b2 indicates whether 1 is selected or not
	int len = cycle.size(), head = cycle[0], sec = cycle[1], last = cycle[len - 1];
	g[sec][0][0] = f[head][0] + f[sec][0];
	g[sec][1][0] = f[head][0] + f[sec][1];
	for(int i = 2; i < len; ++i)
	{
		int u = cycle[i - 1], v = cycle[i];
		g[v][0][0] = max(g[u][0][0], g[u][1][0]) + f[v][0];
		g[v][1][0] = g[u][0][0] + f[v][1];
	}
	g[sec][0][1] = f[head][1] + f[sec][0];
	if(len > 2)
	{
		int third = cycle[2];
		g[third][0][1] = g[sec][0][1] + f[third][0];
		g[third][1][1] = g[sec][0][1] + f[third][1];
	}
	for(int i = 3; i < len; ++i)
	{
		int u = cycle[i - 1], v = cycle[i];
		g[v][0][1] = max(g[u][0][1], g[u][1][1]) + f[v][0];
		g[v][1][1] = g[u][0][1] + f[v][1];
	}
	ans += max(g[last][0][0], max(g[last][0][1], g[last][1][0]));
	return;
}

int main()
{
	n = read();
	for(int i = 1; i <= n; ++i)
	{
		int target;
		a[i] = read();
		target = read();
		addedge(i, target);
		addedge(target, i);
	}
	for(int i = 1; i <= n; ++i)
	{
		if(vis[i])
			continue;  //Notice that the whole picture is a forest
		ok = false;
		while(!stk.empty())
			stk.pop();
		cycle.clear();  //Details, empty stack and dynamic array
		find_circle(i, -1);  //Ring finding
		for(int x : cycle)  //Only C++11 and above standards support
			dp(x, 0);  //Tree DP
		dp2();  //Sequence DP
	}
	printf("%lld\n", ans);
	return 0;
}

Change root DP

Recommended reading: [study notes] change root DP.

I didn't learn this... So I didn't write

Topics: Algorithm data structure Dynamic Programming