C++ Algorithm Advanced Series Analysis Tree Dynamic Programming Algorithm Ideas

1 Introduction

What is Tree Dynamic Programming?

A concept has 2sub-concepts:

  • One is the concept of dynamic programming. 子问题Dynamic programming can be simply understood as obtaining the state value of the current sub-problem by modifying the calculated state value (finding the optimal value of the current sub-problem based on the state value of the sub-problem).

Tips: This article focuses on the application of dynamic programming in the tree structure. For more basic theory and general knowledge about dynamic programming, you can read relevant information.

  • Refers to the tree data structure. The tree-type dynamic programming algorithm refers to the dynamic programming idea provided on the tree logic structure.

The most important part of dynamic programming is to find the state quantities and state transition expressions that change between sub-problems. The state transition process of tree-type dynamic programming is generally transferred from the subtree to the root node, which is also in line with the bottom-up logic of dynamic programming.

This article unveils its mystery by explaining several classic tree dynamic programming cases.

2. Classic case

2.1 A dance without a boss

Prom without a boss is 树型动态规划the classic case. The following will analyze the details of this case, so as to deeply understand the logical flow of tree dynamic programming.

Problem Description:

nThere are employees in a company whose number is 1∼N. In a company, there must be a superior-subordinate relationship. The relationship between employees is a typical tree structure in terms of logical structure.

In a tree structure, a parent node is the immediate boss of a child node. As shown below:

2.png

Now there is a dance party, and every employee invited will add a certain happiness index to the dance party. However, if the boss of a certain employee comes to the dance party, then this employee will not come to the dance party.

The topic requires which employees to invite can maximize the happiness index of the entire dance.

problem analysis:

The following information can be obtained by analyzing the topic:

**First:** Every employee has a weight: happiness index . As shown in the figure below, the numbers in the parentheses of the nodes indicate the happiness index.

3.png

**Second:** Can this problem use dynamic programming algorithm thinking?

Dynamic programming requires subproblems and must have an optimal substructure.

  • 树结构It is a logical structure of sum itself , which exists naturally.子树子问题
  • As for whether there is an optimal substructure, the scale of the problem can be reduced first.

As shown in the figure below, there is now one boss and two subordinates.

4.png

There are two options for standing on the invite side 2:

  • Invite 总经理nodes, so the node is 2the parent of other nodes in the tree, once it is invited, the child nodes will not participate. The happiness index at this time is 10. It is not yet possible to draw conclusions as to whether this program is the optimal program.
  • Not inviting 总经理, the obvious 市场总监optimal value is 11, 技术总监the optimal value is 12. Invitation 市场总监and 技术总监Post Happiness Index is 23.

From the current situation (only 3nodes), it is obvious that choosing not to invite the parent node can get the maximum happiness index: 23.

Through analysis we can see that:

When the optimal value of the child node is known, the final conclusion of the current node can be judged (whether to get the maximum value or not to get the maximum value). Therefore, this problem has an optimal substructure, and it conforms to the bottom-up solution idea of ​​dynamic programming.

Based on the idea of ​​dynamic programming, the optimal happiness index of the bottom node is calculated first, and then the happiness index of the parent node is updated layer by layer. Finally, the optimal solution of the problem is obtained. As shown below:

5.png

For any node, there will be 2an optimal state value.

  • If you go : the happiness index provided is the self index plus the happiness index value when the child node does not go.
  • If not : the larger value between the self-happiness index plus the child node or not.

The state value of each node can be stored with the help of a two-dimensional array.

6.png

Encoding implementation:

The core logic of encoding:

  • Use DFS(深度搜索)the algorithm 叶节点to update upwards from the optimal value to the optimal value of the parent node.
  • The updated (state) values ​​are stored in a 2D state (dp)array.

Design Flow:

#include <iostream>
#include <vector>
using namespace std;
/*
*节点类
*/
struct Emp {
	//员工编号
	int empId;
	//员工姓名
	string empName;
	//权重:快乐指数
	int happy;
	Emp() {

	}
	Emp(int empId,string empName,int happy) {
		this->empId=empId;
		this->empName=empName;
		this->happy=happy;
	}
};

Prom wrapper class without a boss:

/*
*树(舞会)类
*/
class Dance {
	private:
		//使用一维数组存储所有员工
		Emp emps[100];
		//存储员工之间的从属关系
		vector<int> relationship[100];
		//员工数量
		int empCount;
		//员工编号,内部维护
		int num;
		//二维状态数组,行号为员工编号
		int happys[100][2];
	public:
		Dance() {
		}
		Dance(int empCount) {
			this->num=0;
			//员工数量
			this->empCount=empCount;
		}

		/*
		* 添加新员工
		* 返回员工的编号
		*/
		int addEmp(string empName,int happy) {
			Emp emp(this->num,empName,happy);
			this->emps[this->num]=emp;
			return 	this->num++;;
		}

		/*
		*添加员工之间从属关系
		*/
		void addRelation(int from,int to) {
			this->relationship[from].push_back(to);
		}

		/*
		*规定编号为 0 的员工为根节点
		*/
		int getRoot() {
			return 0;
		}

		/*
		* 深度搜索实现
		*树型动态规划
		*/
		void treeDp(int empId) {
			//不去
			happys[empId][0]=0;
			//去
			happys[empId][1]=this->emps[empId].happy;
			for(int subEmpId: this->relationship[empId]) {
				//基于子节点深度搜索
				treeDp(subEmpId) ;
				//搜索完毕 ,更新状态值,如果不去,查找子节点去与不去的最大值
				happys[empId][0] += max(happys[subEmpId][0],happys[subEmpId][1]);
				//去,添加子节点不去时的状态值
				happys[empId][1]+=happys[subEmpId][0];
			}
		}
        /*
        *输出状态值
        */
		void maxHappy() {
			cout<<"各节点的快乐指数状态值:"<<endl; 
			for(int i=0; i<this->num; i++) {
				for(int j=0; j<2; j++) {
					cout<<this->happys[i][j]<<"\t";
				}
				cout<<endl;
			}
			cout<<"最大快乐指数:"<< max(this->happys[0][0],this->happys[0][1] )<<endl;
		}
};

test:

int main() {
	Dance dance(14);
	int root= dance.addEmp("总经理",10) ;
	int sczj= dance.addEmp("市场总监",11) ;
	dance.addRelation(root,sczj);
	int jszj= dance.addEmp("技术总监",12) ;
	dance.addRelation(root,jszj);
	int yyzg= dance.addEmp("运营主管",13) ;
	dance.addRelation(sczj,yyzg);
	int qdzg= dance.addEmp("渠道主管",14) ;
	dance.addRelation(sczj,qdzg);
	int yfzg= dance.addEmp("研发主管",15) ;
	dance.addRelation(jszj,yfzg);
	int sszg= dance.addEmp("实施主管",16) ;
	dance.addRelation(jszj,sszg);
	int yg= dance.addEmp("员工张三",17) ;
	dance.addRelation(yyzg,yg);
	yg= dance.addEmp("员工李四",18) ;
	dance.addRelation(yyzg,yg);
	yg= dance.addEmp("员工王五",9) ;
	dance.addRelation(yyzg,yg);
	yg= dance.addEmp("员工胡六",8) ;
	dance.addRelation(qdzg,yg);
	yg= dance.addEmp("员工周皮",7) ;
	dance.addRelation(yfzg,yg);
	yg= dance.addEmp("员工李杰",19) ;
	dance.addRelation(sszg,yg);
	yg= dance.addEmp("员工吴用",20) ;
	dance.addRelation(sszg,yg);
	dance.treeDp(root);
	dance.maxHappy();
	return 0;
}

Output result:

7.png

2.2 Upgraded version of Prom Without a Boss

Problem Description:

In the above case of the dance party without a boss, the following restrictions are added. Due to size limitations of the venue, the venue can only accommodate at most m(1≤m≤n) 个人. Request the maximum happiness value.

problem analysis:

Compared with the previous question, for each employee, there is a choice to go or not to go. The above uses a two-dimensional array to store ithe happiness index status value when the employee numbered is going or not going. This problem has an additional limit on the number of people. For each employee (node), in addition to considering whether to go or not, it is also necessary to consider how many people he and his subordinates will attend the dance.

So, a new dimension needs to be added, the number of dancers on the team. As shown below:

8.png

If the employee number is used to iindicate, the number of people is used k, and whether to go or not to use j, the state array happys[1][1][0]indicates the optimal happiness index 1when the employee with the number is not going, and the number of people is limited to .1

After knowing the state information, it is necessary to find out the state transition equation.

//编号为 i 的员工不去。p 取值范围为 0~k
f[i][k][0] = max(f[i][k][0], f[i][k - l][0] + max(f[子节点编号][p][0], f[子节点编号][p][1]));
//编号为 i  的员工去。
f[i][k][1] = max(f[i][k][1], f[i][k - l][1] + f[子节点编号][p][0]);

Number implementation:

Based on the above code, modify 2 positions, one is the state array. One is the deep search algorithm.

//省略……

/*
*节点类 省略……
*/

/*
*树(舞会)类
*/
class Dance {
	private:
         //省略……
		//三维状态数组,行号为员工编号
		int happys[100][100][2];
	public:

         //省略…… 
		/*
		* 深度搜索实现
		* 树型动态规划
		* empId:  当前员工编号 
		* count:  限制人数
		*/
		void dfs(int empId,int count) {
			//对于当前节点:去但是人数限制为 0
			happys[empId][0][1] = 0;
			for (int k =count; k; --k)
				//对于当前节点:去,人数限制不同的时候的状态值
				happys[empId][k][1] = happys[empId][k - 1][1] +this->emps[empId].happy;
			//查询子节点信息
			for(int subEmpId: this->relationship[empId]) {
				//基于子节点深度搜索
				dfs(subEmpId,count) ;
				for (int k = count; k >= 0; --k)
					for (int l = 0; l <= k; l++) {
                          //不去
						happys[empId][k][0] = max(happys[empId][k][0], happys[empId][k - l][0] + max(happys[subEmpId][l][0], happys[subEmpId][l][1]));
						happys[empId][k][1] = max(happys[empId][k][1], happys[empId][k - l][1] + happys[subEmpId][l][0]);
					}
			}
		}
         //输出最大快乐指数
		void maxHappy(int count) {
			cout<<"最大快乐指数:"<< max(this->happys[0][count][0],this->happys[0][count][1] )<<endl;
		}
};

int main() {
	Dance dance(14);
    //省略……
	dance.dfs(root,3);
	dance.maxHappy(3);
	return 0;
}

Output result:

9.png

2.3 Castle Guardians

Problem Description:

All the roads of a castle form a ntree of nodes. If a soldier is placed on a node, the edge connected to this node will be guarded. Ask how many soldiers are required to guard all the edges.

problem analysis:

This topic is similar to the above topic on the dance party without a boss. You can also use a two-dimensional array to store the state value of the first node. The state value here refers to the minimum soldier value required by the subtree when the current node is the root node.

soldiers[100][2];
//soldiers[1][0] 表示编号为 1 的节点处不放置士兵时树的士兵总数
//soldiers[1][1] 表示编号为 1 的节点处放置士兵时树的士兵总数

As shown in the figure below, the node status value when there is only one node:

  • For this node, there is 2a state, put a soldier or not put a soldier. When there is only one node, theoretically one soldier can be placed or not, but realistically, at least one soldier needs to be placed. Means min(soldiers[0][1,soldiers[0][0])>0(最小值不能为零), if the result is 0at least one soldier needs to be placed 1.

10.png

  • When the node has child nodes. Then the status value is as shown in the figure below.

11.png

  • As shown in the figure below, according to the idea of ​​tree-type dynamic programming, when the root node is reached, the optimal state value is 2, and only one soldier needs to be placed at Bthe intersection and Cone soldier at the intersection to guard the entire tree.

12.png

Encoding implementation:

The code is very similar to the code in the above case, in order to make some distinctions. In this case, the relationship between nodes uses an adjacency list. Directly upload all the codes, and understand the details by yourself.

#include <iostream>
#include <vector>
using namespace std;
/*
*节点类
*/
struct Crossing {
	//路口编号
	int cid;
	//路口名
	string cname;
	//使用邻接表存储与之相信的节点的编号
	vector<int>  neighbours;
	Crossing() {}
	Crossing(int cid,string cname) {
		this->cid=cid;
		this->cname=cname;
	}
};
/*
*  城堡树
*/
class City {
	private:
		//所有路口
		Crossing crossings[100];
		//路口数量
		int crossingCount;
		//数量
		int count;
		//编号
		int num;
		//二维状态数组,行号为路口编号
		int soldiers[100][2];
	public:
		City() {}
		City(int count) {
			this->num=0;
			//路口数量
			this->crossingCount=count;
		}
		/*
		* 添加路口
		*/
		int addCrossing(string cname) {
			//新路口
			Crossing crossing(this->num,cname);
			//添加
			this->crossings[this->num]=crossing;
			return 	this->num++;;
		}
		/*
		*添加路口间父子关系
		*/
		void addRelation(int from,int to) {
             //邻接表
			this->crossings[from].neighbours.push_back(to);
		}
		/*
		*规定编号为 0 的员工为根节点
		*/
		int getRoot() {
			return 0;
		}
		/*
		* 深度搜索实现
		* 树型动态规划
		*/
		void dfs(int cid) {
			//不放
			soldiers[cid][0]=0;
			//放
			soldiers[cid][1]=1;
			//深度搜索子节点
			for(int i=0; i< this->crossings[cid].neighbours.size(); i++  ) {
				int subId= this->crossings[cid].neighbours[i];
				//基于子节点深度搜索
				dfs(subId) ;
				//放
				soldiers[cid][1]+= soldiers[subId][0];
				//不放
				soldiers[cid][0]+= min(soldiers[subId][1],soldiers[subId][0]) ==0?1:min(soldiers[subId][1],soldiers[subId][0]);
			}
		}
        /*
        *输出
        */
		void outInfo() {
			cout<<"各路口的士兵数:"<<endl;
			for(int i=0; i<this->num; i++) {
				for(int j=0; j<2; j++) {
					cout<<this->soldiers[i][j]<<"\t";
				}
				cout<<endl;
			}
			cout<<"最小士兵人数:"<< min(this->soldiers[0][0],this->soldiers[0][1] )<<endl;
		}
};
//测试
int main() {
	City city(7);
	int root= city.addCrossing("A路口") ;  //0
	int bRoad= city.addCrossing("B路口") ;
	city.addRelation(root,bRoad);
	int cRoad= city.addCrossing("C路口") ;
	city.addRelation(root,cRoad);
	int dRoad= city.addCrossing("D路口") ;
	city.addRelation(bRoad,dRoad);
	int eRoad= city.addCrossing("E路口") ;
	city.addRelation(bRoad,eRoad);
	int fRoad= city.addCrossing("F路口") ;
	city.addRelation(cRoad,fRoad);
	int gRoad= city.addCrossing("G路口") ;
	city.addRelation(cRoad,gRoad);
	city.dfs(root);
	city.outInfo();
	return 0;
}

Output result:

13.png

3. Summary

This article explains tree-type dynamic programming, which is a solution to problems based on tree structure using dynamic programming ideas.

Dynamic programming is a very important algorithm idea, and it is easy to get started, but because it is applicable to many scenarios, it has great variability. Even so, the problem remains the same. If you find the state value and state transition expression of the sub-problem, the problem will be solved.

Guess you like

Origin blog.csdn.net/y6123236/article/details/130280585