树形DP

本文介绍了多个与树形动态规划和二叉树相关的编程问题,包括寻找最大路径和、最深叶子节点、最长连续序列等,并提供了相应的解题思路和代码实现。这些问题涉及到了动态规划、递归和二叉树遍历等算法,展示了如何在二叉树结构中应用这些算法来解决实际问题。
摘要由CSDN通过智能技术生成

目录

一,树形DP

二,OJ实战

CSU 1022 菜鸟和大牛

SCU 1114 数字三角

HDU 2032 杨辉三角

CSU 1010 Water Drinking

力扣 112. 路径总和

力扣 129. 求根到叶子节点数字之和

力扣 298. 二叉树最长连续序列

力扣 549. 二叉树最长连续序列 II

力扣 333. 最大 BST 子树

力扣 823. 带因子的二叉树

力扣 894. 所有可能的真二叉树

力扣 1766. 互质树

力扣 1457. 二叉树中的伪回文路径

力扣 2867. 统计树中的合法路径数目

力扣 2313. 二叉树中得到结果所需的最少翻转次数


一,树形DP

以树作为动态规划的良基集合的问题,称为树形DP

二,OJ实战

CSU 1022 菜鸟和大牛

题目:

Description

blue和AutoGerk是好朋友。他们的相同点是都喜欢研究算法,不同点是AutoGerk已是大牛而blue还是菜鸟。blue经常拿一些自以为很难的问题去问AutoGerk,想难倒他,但是每次AutoGerk都能轻而易举地做出来。就在上个礼拜的星期天下午,AutoGerk正在玩游戏,blue又拿着他的问题来了。AutoGerk一看,依然是如此简单。AutoGerk很想玩他的游戏,但是又不想冷落朋友。于是他介绍你,同样是大牛级的人物,给blue,来回答他的问题。

blue的问题如下:

一个由n行数字组成的三角形,第i行有2i-1个正整数(小于等于1000),如下:

      3
    7 1 4
  2 4 3 6 2
8 5 2 9 3 6 2

要求你用笔从第1行画到第n(0 < n ≤ 100)行,从当前行往下画的时候只能在相邻的数字经过,也就是说,如果从一行的一个数往下画,只能选择其左下或者正下或者右下三个数中的一个(如果存在的话),把所有被画起来的数字相加,得到一个和,求能得到的最大的和的值是多少。

上例中能得到的最大的和为3 + 7 + 4 + 9 = 23.

Input

第一行,一个自然数T,表示总共给出的三角形数,对于每一个三角形,首先给出一个自然数n,表示将输入的三角形有n行。接下来有n行,第i行有2i-1个数字,

Output

对于每个三角形,输出一个数,即能得到的最大的和。

Sample Input

2
2
1
1 2 3
4
3
7 1 4
2 4 3 6 2
8 5 2 9 3 6 2

Sample Output

4
23

代码:

#include<iostream>
#include<algorithm>
using namespace std;
 
int n,num[111][222], ans[111][222];
 
int dp(int i, int j)
{
	if (ans[i][j])return ans[i][j];
	if (i == n)return num[i][j];
	int r = max(dp(i + 1, j), max(dp(i + 1, j + 1), dp(i + 1, j + 2)));
	ans[i][j] = r + num[i][j];
	return ans[i][j];
}
 
int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		cin >> n;
		for (int i = 1; i <= n; i++)for (int j = 1; j <= i * 2 - 1; j++)
		{
			cin >> num[i][j];
			ans[i][j] = 0;
		}
		cout << dp(1, 1) << endl;
	}	
	return 0;
}

SCU 1114 数字三角

题目:

Description

下图是个数字三角,请编写一个程序计算从顶部至底部某处一条路径,使得该路径所经过的数字总和最大。

7

3  8

8  1  0

2  7  4  4

1.  每一步可沿左斜线向下或右斜线向下走;

2.  1<=三角形行数<=100

3.  三角形中的数字为整数 0,1,……,99。

4.  如果有多种情况结果都最大,任意输出一种即可。

输入:

第一行一个整数N,代表三角形的行数。

接下来N行,描述了一个数字三角。

输出:

    第一行一个整数,代表路径所经过底数字总和。

    第二行N个数,代表所经过的数字。

样例:

输入:


7
3 8
8 1 0
2 7 4 4

输出:

25 
7 3 8 7

代码:

#include<iostream>
#include<string.h>
#include<stack>
using namespace std;
 
 
int main()
{	
	int n;
	cin >> n;
	int list[101][101];
	memset(list, 0, sizeof(list));
	cin >> list[1][1];
	for (int i = 2; i <= n; i++)
	{
		for (int j = 1; j <= i; j++)
		{
			cin >> list[i][j];
list[i][j] += (list[i - 1][j] > list[i - 1][j - 1]) ? list[i - 1][j] : list[i - 1][j - 1];
		}
	}
	int max = 0, key = 0;
	for (int j = 1; j <= n; j++)
	{
		if (max < list[n][j])
		{
			max = list[n][j];
			key = j;
		}
	}
	cout << max << endl;
	stack<int>s;
	for (int i = n; i>0; i--)
	{
		if (list[i - 1][key] > list[i - 1][key - 1])
		     s.push(list[i][key] - list[i - 1][key]);
		else s.push(list[i][key] - list[i-1][(key--)-1]);
	}
	while (!s.empty())
	{
		cout << s.top()<<" ";
		s.pop();
	}
	return 0;
}

HDU 2032 杨辉三角

题目:

还记得中学时候学过的杨辉三角吗?具体的定义这里不再描述,你可以参考以下的图形: 

1 1 
1 2 1 
1 3 3 1 
1 4 6 4 1 
1 5 10 10 5 1 

Input输入数据包含多个测试实例,每个测试实例的输入只包含一个正整数n(1<=n<=30),表示将要输出的杨辉三角的层数。Output对应于每一个输入,请输出相应层数的杨辉三角,每一层的整数之间用一个空格隔开,每一个杨辉三角后面加一个空行。Sample Input

2 3

Sample Output

1
1 1

1
1 1
1 2 1

思路:

动态规划

和0-1背包差不多,可以使用空间压缩

代码:

#include<iostream>
using namespace std;
 
int main()
{
	int n, ans[40];
	while (cin >> n)
	{
		for (int i = 0; i <= n; i++)ans[i] = 0;
		ans[1] = 1;
		for (int r = 1; r <= n; r++)
		{
			cout << 1;
			for (int i = r; i > 0; i--)ans[i] += ans[i - 1];
			for (int i = 2; i <= r; i++)cout << " " << ans[i];
			cout << endl;
		}
		cout << endl;
	}
	return 0;
}

CSU 1010 Water Drinking

题目:

Description
The Happy Desert is full of sands. There is only a kind of animal called camel living on the Happy Desert. Cause they live here, they need water here. Fortunately, they find a pond which is full of water in the east corner of the desert. Though small, but enough. However, they still need to stand in lines to drink the water in the pond.

Now we mark the pond with number 0, and each of the camels with a specific number, starting from 1. And we use a pair of number to show the two adjacent camels, in which the former number is closer to the pond. There may be more than one lines starting from the pond and ending in a certain camel, but each line is always straight and has no forks.

Input
There’re multiple test cases. In each case, there is a number N (1≤N≤100000) followed by N lines of number pairs presenting the proximate two camels. There are 99999 camels at most.

Output
For each test case, output the camel’s number who is standing in the last position of its line but is closest to the pond. If there are more than one answer, output the one with the minimal number.

Sample Input
1
0 1
5
0 2
0 1
1 4
2 3
3 5
5
1 3
0 2
0 1
0 4
4 5
Sample Output
1
4
2
题意:

给出一棵树,找出深度最小的叶子节点,

如果有多个这样的叶子节点,输出编号最小的那个。

直接用静态链表建立树,再用DP求出每个节点的深度即可。

代码:

#include<iostream>
#include<stdio.h>
using namespace std;
 
int fa[100005], deep[100005], isfa[100005];
 
int getdeep(int k)
{
	if (deep[k])return deep[k];
	int d = getdeep(fa[k]);
	deep[k] = d + 1;
	return deep[k];
}
 
int main()
{
	int n, a, b;
	while (scanf("%d", &n)!=EOF)
	{
		for (int i = 0; i <= n; i++)isfa[i] = 0;
		for (int i = 0; i < n; i++)
		{
			scanf("%d%d", &a, &b);
			fa[b] = a, isfa[a] = 1;
		}
		for (int i = 0; i <= n; i++)deep[i] = 0;
		deep[0] = 1;
		for (int i = 0; i <= n; i++)getdeep(i);
		int minn = 100005;
		for (int i = 1; i <= n; i++)if (isfa[i] == 0 && minn > deep[i])minn = deep[i];
		for (int i = 1; i <= n; i++)if (isfa[i] == 0 && minn == deep[i])
		{
			printf("%d\n", i);
			break;
		}
	}
	return 0;
}

力扣 112. 路径总和

题目:

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

示例: 
给定如下二叉树,以及目标和 sum = 22,

              5
             / \
            4   8
           /   / \
          11  13  4
         /  \      \
        7    2      1
返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。

代码:

class Solution {
public:
	bool hasPathSum(TreeNode* root, int sum) {
		if (!root)return false;
		sum -= root->val;
		if (!root->left && !root->right)return !sum;
		return hasPathSum(root->left, sum) || hasPathSum(root->right, sum);
	}
};

力扣 129. 求根到叶子节点数字之和

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。

例如,从根到叶子节点路径 1->2->3 代表数字 123。

计算从根到叶子节点生成的所有数字之和。

说明: 叶子节点是指没有子节点的节点。

示例 1:

输入: [1,2,3]
    1
   / \
  2   3
输出: 25
解释:
从根到叶子节点路径 1->2 代表数字 12.
从根到叶子节点路径 1->3 代表数字 13.
因此,数字总和 = 12 + 13 = 25.
示例 2:

输入: [4,9,0,5,1]
    4
   / \
  9   0
 / \
5   1
输出: 1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495.
从根到叶子节点路径 4->9->1 代表数字 491.
从根到叶子节点路径 4->0 代表数字 40.
因此,数字总和 = 495 + 491 + 40 = 1026.

代码:

class Solution {
public:
	void sumNumbers(TreeNode* root, int cur,int &ans) {
		if (!root)return;
		cur = cur * 10 + root->val;
		if (!root->left && !root->right)
		{
			ans += cur;
			return;
		}
		sumNumbers(root->left, cur, ans);
		sumNumbers(root->right, cur, ans);
	}
	int sumNumbers(TreeNode* root) {
		int ans = 0;
		sumNumbers(root, 0, ans);
		return ans;
	}
};

PS:

这题和另外一题基本一样:力扣 OJ 988. 从叶结点开始的最小字符串

力扣 298. 二叉树最长连续序列

给你一棵指定的二叉树的根节点 root ,请你计算其中 最长连续序列路径 的长度。

最长连续序列路径 是依次递增 1 的路径。该路径,可以是从某个初始节点到树中任意节点,通过「父 - 子」关系连接而产生的任意路径。且必须从父节点到子节点,反过来是不可以的。

 
示例 1:


输入:root = [1,null,3,2,4,null,null,null,5]
输出:3
解释:当中,最长连续序列是 3-4-5 ,所以返回结果为 3 。
示例 2:


输入:root = [2,null,3,2,null,1]
输出:2
解释:当中,最长连续序列是 2-3 。注意,不是 3-2-1,所以返回 2 。

提示:

树中节点的数目在范围 [1, 3 * 104] 内
-3 * 104 <= Node.val <= 3 * 104

class Solution {
public:
	int ans = 0;
	int dfs(TreeNode* root) {
		if (!root)return 0;
		int al = dfs(root->left);
		int ar = dfs(root->right);
		int n = 1;
		if (root->left && root->val + 1 == root->left->val) {
			n = al + 1;
		}
		if (root->right && root->val + 1== root->right->val) {
			n = max(n,ar+1);
		}
		ans = max(ans, n);
		return n;
	}
	int longestConsecutive(TreeNode* root) {
		dfs(root);
		return ans;
	}
};

力扣 549. 二叉树最长连续序列 II

给定二叉树的根 root ,返回树中最长连续路径的长度。
连续路径是路径中相邻节点的值相差 1 的路径。此路径可以是增加或减少。

  • 例如, [1,2,3,4] 和 [4,3,2,1] 都被认为有效,但路径 [1,2,4,3] 无效。

另一方面,路径可以是子-父-子顺序,不一定是父子顺序。

示例 1:

输入: root = [1,2,3]
输出: 2
解释: 最长的连续路径是 [1, 2] 或者 [2, 1]。

示例 2:

输入: root = [2,1,3]
输出: 3
解释: 最长的连续路径是 [1, 2, 3] 或者 [3, 2, 1]。

提示:

  • 树上所有节点的值都在 [1, 3 * 104] 范围内。
  • -3 * 104 <= Node.val <= 3 * 104
class Solution {
public:
    int longestConsecutive(TreeNode* root) {
        dp(root,m1,1);
        dp(root,m2,-1);
        ans=0;
        dfs(root);
        return ans;
    }
    map<TreeNode*,int>m1,m2;
    int ans;
    int dp(TreeNode* root,map<TreeNode*,int>&m,int dif)
    {
        int ans=1;
        if(root->left){
            int x=dp(root->left,m,dif);
            if(root->left->val==root->val-dif)ans=x+1;
        }
        if(root->right){
            int x=dp(root->right,m,dif);
            if(root->right->val==root->val-dif)ans=max(ans,x+1);
        }
        return m[root]=ans;
    }
    void dfs(TreeNode* root){
        ans=max(ans,m1[root]+m2[root]-1);
        cout<<m1[root]<<" "<<m2[root]<<endl;
        if(root->left)dfs(root->left);
        if(root->right)dfs(root->right);
    }
};

力扣 333. 最大 BST 子树

给定一个二叉树,找到其中最大的二叉搜索树(BST)子树,并返回该子树的大小。其中,最大指的是子树节点数最多的。

二叉搜索树(BST)中的所有节点都具备以下属性:

左子树的值小于其父(根)节点的值。

右子树的值大于其父(根)节点的值。

注意:子树必须包含其所有后代。

示例 1:

输入:root = [10,5,15,1,8,null,7]
输出:3
解释:本例中最大的 BST 子树是高亮显示的子树。返回值是子树的大小,即 3 。
示例 2:

输入:root = [4,2,7,2,3,5,null,2,null,null,null,null,null,1]
输出:2
 

提示:

树上节点数目的范围是 [0, 104]
-104 <= Node.val <= 104
 

进阶:  你能想出 O(n) 时间复杂度的解法吗?

class Solution {
public:
	int ans = 0;
	int dfs(TreeNode* root,int &low,int &high)
	{
		if (!root)return 0;
		int low1, high1, low2, high2;
		int al = dfs(root->left, low1, high1);
		int ar = dfs(root->right, low2, high2);
		low = root->val, high = root->val;
		if (root->left) {
			if (high1 >= root->val)return 0;
			if (al == 0)return 0;
			low = low1;
		}
		if (root->right) {
			if (low2 <= root->val)return 0;
			if (ar == 0)return 0;
			high = high2;
		}
		int n = al + ar + 1;
		ans = max(ans, n);
		return n;
	}
	int largestBSTSubtree(TreeNode* root) {
		int low, high;
		dfs(root, low, high);
		return ans;
	}
};

力扣 823. 带因子的二叉树

给出一个含有不重复整数元素的数组 arr ,每个整数 arr[i] 均大于 1。

用这些整数来构建二叉树,每个整数可以使用任意次数。其中:每个非叶结点的值应等于它的两个子结点的值的乘积。

满足条件的二叉树一共有多少个?答案可能很大,返回 对 109 + 7 取余 的结果。

示例 1:

输入: arr = [2, 4]
输出: 3
解释: 可以得到这些二叉树: [2], [4], [4, 2, 2]

示例 2:

输入: arr = [2, 4, 5, 10]
输出: 7
解释: 可以得到这些二叉树: [2], [4], [5], [10], [4, 2, 2], [10, 2, 5], [10, 5, 2].

提示:

  • 1 <= arr.length <= 1000
  • 2 <= arr[i] <= 109
  • arr 中的所有值 互不相同
class Solution {
public:
	int numFactoredBinaryTrees(int x) {
		int ans = 1;
		for (auto it : dp) {
			if (x%it.first == 0 && dp.find(x / it.first) != dp.end())ans = (ans + it.second * dp[x / it.first] % p) % p;
		}
		return ans;
	}
	int numFactoredBinaryTrees(vector<int>& arr) {
		sort(arr.begin(), arr.end());
		int ans = 0;
		for (auto x : arr) {
			dp[x] = numFactoredBinaryTrees(x);
			ans = (ans + dp[x]) % p;
		}
		return ans;
	}
	map<int, long long>dp;
	int p = 1000000007;
};

力扣 894. 所有可能的真二叉树

给你一个整数 n ,请你找出所有可能含 n 个节点的 真二叉树 ,并以列表形式返回。答案中每棵树的每个节点都必须符合 Node.val == 0 。

答案的每个元素都是一棵真二叉树的根节点。你可以按 任意顺序 返回最终的真二叉树列表

真二叉树 是一类二叉树,树中每个节点恰好有 0 或 2 个子节点。

示例 1:

输入:n = 7
输出:[[0,0,0,null,null,0,0,null,null,0,0],[0,0,0,null,null,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,null,null,null,null,0,0],[0,0,0,0,0,null,null,0,0]]

示例 2:

输入:n = 3
输出:[[0,0,0]]

提示:

  • 1 <= n <= 20
class Solution {
public:
	Solution() {
		m[1] = vector<TreeNode*>{ new TreeNode() };
		for (int i = 3; i < 20; i += 2) {
			for (int j = 1; j < i; j += 2) {
				int k = i - 1 - j;
				for (auto ti : m[j]) {
					for (auto tk : m[k]) {
						auto t = new TreeNode ();
						t->left = ti, t->right = tk;
						m[i].push_back(t);
					}
				}
			}
		}
	}
	vector<TreeNode*> allPossibleFBT(int n) {
		return m[n];
	}
	map<int, vector<TreeNode*>>m;
};

力扣 1766. 互质树

给你一个 n 个节点的树(也就是一个无环连通无向图),节点编号从 0 到 n - 1 ,且恰好有 n - 1 条边,每个节点有一个值。树的 根节点 为 0 号点。

给你一个整数数组 nums 和一个二维数组 edges 来表示这棵树。nums[i] 表示第 i 个点的值,edges[j] = [uj, vj] 表示节点 uj 和节点 vj 在树中有一条边。

当 gcd(x, y) == 1 ,我们称两个数 x 和 y 是 互质的 ,其中 gcd(x, y) 是 x 和 y 的 最大公约数 。

从节点 i 到  最短路径上的点都是节点 i 的祖先节点。一个节点 不是 它自己的祖先节点。

请你返回一个大小为 n 的数组 ans ,其中 ans[i]是离节点 i 最近的祖先节点且满足 nums[i] 和 nums[ans[i]] 是 互质的 ,如果不存在这样的祖先节点,ans[i] 为 -1 。

示例 1:

输入:nums = [2,3,3,2], edges = [[0,1],[1,2],[1,3]]
输出:[-1,0,0,1]
解释:上图中,每个节点的值在括号中表示。
- 节点 0 没有互质祖先。
- 节点 1 只有一个祖先节点 0 。它们的值是互质的(gcd(2,3) == 1)。
- 节点 2 有两个祖先节点,分别是节点 1 和节点 0 。节点 1 的值与它的值不是互质的(gcd(3,3) == 3)但节点 0 的值是互质的(gcd(2,3) == 1),所以节点 0 是最近的符合要求的祖先节点。
- 节点 3 有两个祖先节点,分别是节点 1 和节点 0 。它与节点 1 互质(gcd(3,2) == 1),所以节点 1 是离它最近的符合要求的祖先节点。

示例 2:

输入:nums = [5,6,10,2,3,6,15], edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]]
输出:[-1,0,-1,0,0,0,-1]

提示:

  • nums.length == n
  • 1 <= nums[i] <= 50
  • 1 <= n <= 105
  • edges.length == n - 1
  • edges[j].length == 2
  • 0 <= uj, vj < n
  • uj != vj
class Solution {
public:
    Solution(){
        for(int i=1;i<=50;i++)for(int j=1;j<=50;j++){
            if(gcd(i,j)==1)gcdIsOne[i].push_back(j);
        }
        deep[-1]=-1;
    }
    vector<int> getCoprimes(vector<int>& nums, vector<vector<int>>& edges) {
        map<int,vector<int>>m;
        for(auto e:edges)m[e[0]].push_back(e[1]),m[e[1]].push_back(e[0]);
        vector<int>ans(nums.size());
        vector<int>ids(nums.size(),-1); //nums的反射,记录最近的id
        for(int i=0;i<50;i++)ids.push_back(-1);
        dfs(nums,m,0,-1,0,ids,ans);
        return ans;
    }
private:
    void dfs(vector<int>& nums,map<int,vector<int>>&m,int r,int fa,int d,vector<int>&ids,vector<int>&ans){
        int maxDeep=-1;
        ans[r]=-1;
        for(auto g:gcdIsOne[nums[r]]){
          if(maxDeep<deep[ids[g]])maxDeep=deep[ids[g]],ans[r]=ids[g];
        }
        deep[r]=d;
        for(auto id:m[r]){
            if(id==fa)continue;
            int tmp = ids[nums[r]];
            ids[nums[r]]=r;
            dfs(nums,m,id,r,d+1,ids,ans);
            ids[nums[r]]=tmp;
        }
    }
    int gcd(int x,int y){
        if(y==0)return x;
        return gcd(y,x%y);
    }
private:
    map<int,vector<int>>gcdIsOne;
    map<int,int>deep;
};

力扣 1457. 二叉树中的伪回文路径

给你一棵二叉树,每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的,当它满足:路径经过的所有节点值的排列中,存在一个回文序列。

请你返回从根到叶子节点的所有路径中 伪回文 路径的数目。

示例 1:

输入:root = [2,3,1,3,1,null,1]
输出:2 
解释:上图为给定的二叉树。总共有 3 条从根到叶子的路径:红色路径 [2,3,3] ,绿色路径 [2,1,1] 和路径 [2,3,1] 。
     在这些路径中,只有红色和绿色的路径是伪回文路径,因为红色路径 [2,3,3] 存在回文排列 [3,2,3] ,绿色路径 [2,1,1] 存在回文排列 [1,2,1] 。
示例 2:

输入:root = [2,1,1,1,3,null,null,null,null,null,1]
输出:1 
解释:上图为给定二叉树。总共有 3 条从根到叶子的路径:绿色路径 [2,1,1] ,路径 [2,1,3,1] 和路径 [2,1] 。
     这些路径中只有绿色路径是伪回文路径,因为 [2,1,1] 存在回文排列 [1,2,1] 。
示例 3:

输入:root = [9]
输出:1
 

提示:

给定二叉树的节点数目在 1 到 10^5 之间。
节点值在 1 到 9 之间。

class Solution {
public:
    int f(TreeNode* root,int n)
    {
        n^=(1<< root->val);
        if(root->right && root->left)return f(root->right,n)+f(root->left,n);
        if(root->right)return f(root->right,n);
        if(root->left)return f(root->left,n);
        return (n&-n)==n;
    }
    int pseudoPalindromicPaths (TreeNode* root) {
        return f(root,0);
    }
};

力扣 2867. 统计树中的合法路径数目

给你一棵 n 个节点的无向树,节点编号为 1 到 n 。给你一个整数 n 和一个长度为 n - 1 的二维整数数组 edges ,其中 edges[i] = [ui, vi] 表示节点 ui 和 vi 在树中有一条边。

请你返回树中的 合法路径数目 。

如果在节点 a 到节点 b 之间 恰好有一个 节点的编号是质数,那么我们称路径 (a, b) 是 合法的 。

注意:

  • 路径 (a, b) 指的是一条从节点 a 开始到节点 b 结束的一个节点序列,序列中的节点 互不相同 ,且相邻节点之间在树上有一条边。
  • 路径 (a, b) 和路径 (b, a) 视为 同一条 路径,且只计入答案 一次 。

示例 1:

输入:n = 5, edges = [[1,2],[1,3],[2,4],[2,5]]
输出:4
解释:恰好有一个质数编号的节点路径有:
- (1, 2) 因为路径 1 到 2 只包含一个质数 2 。
- (1, 3) 因为路径 1 到 3 只包含一个质数 3 。
- (1, 4) 因为路径 1 到 4 只包含一个质数 2 。
- (2, 4) 因为路径 2 到 4 只包含一个质数 2 。
只有 4 条合法路径。

示例 2:

输入:n = 6, edges = [[1,2],[1,3],[2,4],[3,5],[3,6]]
输出:6
解释:恰好有一个质数编号的节点路径有:
- (1, 2) 因为路径 1 到 2 只包含一个质数 2 。
- (1, 3) 因为路径 1 到 3 只包含一个质数 3 。
- (1, 4) 因为路径 1 到 4 只包含一个质数 2 。
- (1, 6) 因为路径 1 到 6 只包含一个质数 3 。
- (2, 4) 因为路径 2 到 4 只包含一个质数 2 。
- (3, 6) 因为路径 3 到 6 只包含一个质数 3 。
只有 6 条合法路径。

提示:

  • 1 <= n <= 105
  • edges.length == n - 1
  • edges[i].length == 2
  • 1 <= ui, vi <= n
  • 输入保证 edges 形成一棵合法的树。
class Solution {
public:
	long long countPaths(int n, vector<vector<int>>& edges) {
		auto adjaList = UndirectedGraphData<>(edges).adjaList;
		MultiTreeNode* root = EdgesToMultiTree(adjaList);
		m.clear();
		m0.clear();
		m1.clear();
		dfs(root);
		dp0(root);
		dp1(root);
		return countPaths(root);
	}
	map<int, int>m;
	map<int, long long>m0;
	map<int, int>m1;
	void dfs(MultiTreeNode* root) {
		m[root->val] = root->val>1 && IsPrime(root->val);
		for (auto p : root->son)dfs(p);		
	}
	int dp0(MultiTreeNode* root) {
		int s = 1;
		for (auto p : root->son)s += dp0(p);
		if (m[root->val])return 0;
		return m0[root->val] = s;
	}
	int dp1(MultiTreeNode* root) {
		int s = 1, s2 = 1;
		for (auto p : root->son)s += dp1(p), s2 += m0[p->val];
		if (m[root->val])return m1[root->val] = s2;
		return m1[root->val] = s;
	}
	long long countPaths(MultiTreeNode* root)
	{
		long long s = 0;
		for (auto p : root->son)s += countPaths(p);
		if (m[root->val]) {
			int sm0 = m1[root->val]-1;
			int odds = 0;
			for (auto p : root->son)s += m0[p->val] * (sm0 - m0[p->val]) / 2, odds += m0[p->val] * (sm0 + 1) % 2;
			s += odds / 2 + sm0;
		}
		else {
			int sm0 = m0[root->val];
			for (auto p : root->son)s += (m1[p->val] - m0[p->val])* (sm0 - m0[p->val]);
		}
		return s;
	}
};

力扣 2313. 二叉树中得到结果所需的最少翻转次数

给定二叉树的根 root,具有以下属性:

  • 叶节点 的值为 0 或 1,分别表示 false 和 true
  • 非叶节点的值为 2345,分别表示布尔运算 ORANDXORNOT

您还将得到一个布尔型 result,这是 root 节点的期望 评价 结果。

对节点的评价计算如下:

  • 如果节点是叶节点,则评价是节点的 ,即 true 或 false.
  • 否则, 将其值的布尔运算应用于子节点的 评价,该节点的 评价 即为布尔运算后的结果。

在一个操作中,您可以 翻转 一个叶节点,这将导致一个 false 节点变为 true 节点,一个 true 节点变为 false 节点。

返回需要执行的最小操作数,以使 root 的评价得到 result。可以证明,总有办法达到 result

叶节点 是没有子节点的节点。

注意: NOT 节点只有左孩子或只有右孩子,但其他非叶节点同时拥有左孩子和右孩子。

示例 1:

输入: root = [3,5,4,2,null,1,1,1,0], result = true
输出: 2
解释:
可以证明,至少需要翻转 2 个节点才能使树的 root 评价为 true。上面的图显示了实现这一目标的一种方法。

示例 2:

输入: root = [0], result = false
输出: 0
解释:
树的 root 的评价已经为 false,所以 0 个节点必须翻转。

提示:

  • 树中的节点数在 [1, 105] 范围内。
  • 0 <= Node.val <= 5
  • ORANDXOR 节点有 2 个子节点。
  • NOT 只有一个 1 子节点。
  • 叶节点的值为 0 或 1.
  • 非叶节点的值为2345.

unordered_map<TreeNode*, int>m[2];
int minimumFlip(TreeNode* root, bool r);
auto funcs = std::vector<std::function<int(TreeNode*, bool)>>{
		[](TreeNode* root, bool r)->int {
			return r ? 1 : 0;
		},
		[](TreeNode* root, bool r)->int {
			return r ? 0 : 1;
		},
		[](TreeNode* root, bool r)->int {
			return r ? min(minimumFlip(root->left, true), minimumFlip(root->right,true)) :
				minimumFlip(root->left, false) + minimumFlip(root->right, false);
		},
		[](TreeNode* root, bool r)->int {
			return r ? minimumFlip(root->left, true) + minimumFlip(root->right, true) :
				min(minimumFlip(root->left, false), minimumFlip(root->right, false));
		},
		[](TreeNode* root, bool r)->int {
			return min(minimumFlip(root->left, false) + minimumFlip(root->right, r),
				minimumFlip(root->left, true) + minimumFlip(root->right, !r));
		},
		[](TreeNode* root, bool r)->int {
			return root->left ? minimumFlip(root->left, !r) :
				minimumFlip(root->right, !r);
		}
};
int minimumFlip(TreeNode* root, bool r) {
	if (m[r].find(root) != m[r].end())return m[r][root];
	return m[r][root] = funcs[root->val](root, r);
}
class Solution {
public:
	Solution() {
		m[0].clear();
		m[1].clear();
	}
	int minimumFlips(TreeNode* root, bool r) {
		return minimumFlip(root, r);
	}
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值