树 形 DP (dnf序)

二叉搜索子树的最大键值

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    class info{
        public:
         int maxnum;
         int minnum;
         bool isbst;
         int sum;
         int maxbstsum;
         info(int a,int b,bool c,int d,int e){
            maxnum=a;
            minnum=b;
            isbst=c;
            sum=d;
            maxbstsum=e;
         }
    };
    info f(TreeNode* root){
        if(root==nullptr)
        return info(INT_MIN,INT_MAX,true,0,0);
        info infol=f(root->left);
        info infor=f(root->right);
        int maxn=max(root->val,max(infol.maxnum,infor.maxnum));
        int minn=min(root->val,min(infol.minnum,infor.minnum));
        int sumn=root->val+infol.sum+infor.sum;
        bool isbetn=infol.isbst&&infor.isbst&&root->val<infor.minnum&&root->val>infol.maxnum;
        int maxbetnumn=max(infol.maxbstsum,infor.maxbstsum);
        if(isbetn)
        maxbetnumn=max(maxbetnumn,sumn);
        return info(maxn,minn,isbetn,sumn,maxbetnumn);

    }

    int maxSumBST(TreeNode* root) {
        return f(root).maxbstsum;

    }
};

递归求从头节点向下的最大的最大子树键值和,而到一个头节点要求其最大的键值和:如果不选头节点,需要子树上的最大键值和;如果选头节点,需要知道子树是否为二叉搜索树和子树所有节点的和,并且还需要子树上的最大值和最小值和头节点比较判断是否可以选头节点。最后把所有需要的信息整合到一起传递给父节点

 二叉树的直径

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    class info {
    public:
        int height;
        int diameter;
        info(int a, int b) {
            diameter = a;
            height = b;
        }
    };
    info f(TreeNode* cur) {
        if (cur == nullptr)
            return info(0, 0);
        info infol = f(cur->left);
        info infor = f(cur->right);
        int dia = max(infol.diameter, infor.diameter);
        dia = max(dia, infol.height + infor.height);
        int hei = max(infol.height, infor.height) + 1;
        return info(dia, hei);
    }
    int diameterOfBinaryTree(TreeNode* root) { return f(root).diameter; }
};

从一个头节点及其子树求最大的直径:如果不选头节点,需要其子树的最大直径和;如果选头节点,需要子树的高度

在二叉树中分配金币

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    int ans = 0;
    int f(TreeNode* root) {
        if (root == nullptr)
            return 0;
        int d = f(root->left) + f(root->right) + root->val - 1;
        ans += abs(d);
        return d;
    }
    int distributeCoins(TreeNode* root) {
        f(root);
        return ans;
    }
};

所有硬币的移动路径之和,是由每条边经过的次数累加和组成,所以只要求出每条边的经过次数求和即可。求一个子树所有边的经过次数之和,需要其子树的所有经过边之和,还有每个子树的节点与其子树上的硬币数量,做差的绝对值就是从头节点的子树到头节点之间的那条边经过的次数

没有上司的舞会

#include <iostream>
#include <vector>
using namespace std;
const int maxn = 6002;
int cnt;
int head[maxn];
int to[maxn];
int nex[maxn];
int happy[maxn];
bool laoda[maxn];
int yes[maxn];
int no[maxn];
void build(int n) {
	cnt = 1;
	for (int i = 1; i <= n; i++) {
		head[i] = 0;
		happy[i] = 0;
		laoda[i] = true;
		yes[i] = 0;
		no[i] = 0;
	}
}
void insert(int u, int v) {
	nex[cnt] = head[u];
	to[cnt] = v;
	head[u] = cnt++;
}

void f(int cur) {
	no[cur] = 0;
	yes[cur] = happy[cur];
	for (int edge = head[cur]; edge > 0; edge = nex[edge]) {
		int v = to[edge];
		f(v);
		no[cur]  += max(no[v], yes[v]);
		yes[cur] += no[v];
	}

}
int main() {
	int n;
	cin >> n;
	build(n);
	for (int i = 1; i <= n; i++) {
		int a;
		cin >> a;
		happy[i] = a;
	}
	for (int i = 1; i < n; i++) {
		int a, b;
		cin >> a >> b;
		insert(b,a);
		laoda[a] = false;
	}
	int head;
	for (int i = 1; i <= n; i++)
		if (laoda[i] == true) {
			head = i;
			break;
		}
	f(head);
	cout << max(yes[head], no[head]);
	return 0;


}

 求一个树的最大快乐值:如果不选头节点,只需要其子树:选子树头节点的最大快乐值和不选子树头节点的最大快乐值;如果选头节点,需要不选子树头节点的最大快乐值

 监控二叉树

public class Solution {

    // 提交如下的方法
    public int minCameraCover(TreeNode root) {
        ans = 0;
        if (f(root) == 0) {
            ans++;
        }
        return ans;
    }

    // 遍历过程中一旦需要放置相机,ans++
    public static int ans;

    // 递归含义
    // 假设x上方一定有父亲的情况下,这个假设很重要
    // x为头的整棵树,最终想都覆盖,
    // 并且想使用最少的摄像头,x应该是什么样的状态
    // 返回值含义
    // 0: x是无覆盖的状态,x下方的节点都已经被覆盖
    // 1: x是覆盖状态,x上没摄像头,x下方的节点都已经被覆盖
    // 2: x是覆盖状态,x上有摄像头,x下方的节点都已经被覆盖
    private int f(TreeNode x) {
        if (x == null) {
            return 1;
        }
        int left = f(x.left);
        int right = f(x.right);
        if (left == 0 || right == 0) {
            ans++;
            return 2;
        }
        if (left == 1 && right == 1) {
            return 0;
        }
        return 1;
    }

}

一个节点的情况:1.没有被监控覆盖  2.被覆盖但没有放监控  3.被覆盖并且放了监控

根据不同的节点情况讨论即可

路径总和

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int ans;;
    unordered_map<long ,int>umap;
    void f(TreeNode* cur,long sum,int targetSum){
         if(cur==nullptr)return ;
         sum+=cur->val;
         long need=sum-targetSum;
         if(umap.find(need)!=nullptr) ans+=umap[need];
         umap[sum]++;
         f(cur->left,sum,targetSum);
         f(cur->right,sum,targetSum);
         umap[sum]--;
         
    }
    int pathSum(TreeNode* root, int targetSum) {
            umap.clear();
            ans=0;
            umap[0]=1;
            f(root,0,targetSum);
            return ans;
            
    }
};

树形dp不仅父亲节点需要子节点信息,也可以从父节点向子节点传递信息。求target的路径,如果知道之前遍历过的所有的节点之和,和之前所有从根节点到遍历过的节点的的路径和的路径个数,求出sum-target的路径个数累加即可

到达首都的最少耗油量

public class Solution {

    public static long minimumFuelCost(int[][] roads, int seats) {
        int n = roads.length + 1;
        ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            graph.add(new ArrayList<>());
        }
        for (int[] r : roads) {
            graph.get(r[0]).add(r[1]);
            graph.get(r[1]).add(r[0]);
        }
        int[] size = new int[n];
        long[] cost = new long[n];
        f(graph, seats, 0, -1, size, cost);
        return cost[0];
    }

    // 根据图,当前来到u,u的父节点是p
    // 遍历完成后,请填好size[u]、cost[u]
    public static void f(ArrayList<ArrayList<Integer>> graph, int seats, int u, int p, int[] size, long[] cost) {
        size[u] = 1;
        for (int v : graph.get(u)) {
            if (v != p) {
                f(graph, seats, v, u, size, cost);

                size[u] += size[v];
                cost[u] += cost[v];
                // a/b向上取整,可以写成(a+b-1)/b
                // (size[v]+seats-1) / seats = size[v] / seats 向上取整
                cost[u] += (size[v] + seats - 1) / seats;
            }
        }
    }

}

求到达头节点a的最少油量,需要其子树的最少油量,还需要到达其子树的头节点的人的个数,人数对seats求上限加子树的油量就是到达a的最少油量

相邻字符不同的最长路径

class Solution {
public:
    vector<vector<int>> pragh;
    void build(int n) {
        for (int i = 0; i < n; i++)
            pragh.push_back(vector<int>());
    }
    void insert(int u, int v) { pragh[u].push_back(v); }
    class info {
    public:
        int maxhead;
        int maxleng;
        info(int a, int b) {
            maxhead = a;
            maxleng = b;
        }
    };

    info f(string& s, int cur) {
        if (pragh[cur].empty())
            return info(1, 1);
        int max1 = 0, max2 = 0;
        int maxheight = 0;
        for (int edge : pragh[cur]) {
            info rem = f(s, edge);
            maxheight = max(maxheight, rem.maxleng);
            if (s[cur] != s[edge]) {
                if (rem.maxhead > max1) {
                    max2 = max1;
                    max1 = rem.maxhead;
                } else if (rem.maxhead > max2) {
                    max2 = rem.maxhead;
                }
            }
        }
        maxheight = max(maxheight, max1 + max2 + 1);
        int maxn = max1 + 1;
        return info(maxn, maxheight);
    }
    int longestPath(vector<int>& parent, string s) {
        int n = parent.size();
        build(n);
        for (int i = 1; i < n; i++) {
            insert(parent[i], i);
        }
        return f(s, 0).maxleng;
    }
};

求头节点a的子树的最大路径:如果不选a,需a的子树的最大路径和;如果选a,需要子树的头节点向下延申的最大长度

  移除子树后的二叉树高度

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left),
 * right(right) {}
 * };
 */
class Solution {
public:
    static const int maxn = 100005;
    int dfn[maxn];
    int size[maxn];
    int deep[maxn];
    int maxl[maxn];
    int maxr[maxn];
    int dfnnum;
    void f(TreeNode* cur, int k) {
        int i = ++dfnnum;
        dfn[cur->val] = i;
        deep[i] = k;
        size[i] = 1;
        if (cur->left != nullptr) {
            f(cur->left, k + 1);
            size[i] += size[dfn[cur->left->val]];
        }

        if (cur->right != nullptr) {
            f(cur->right, k + 1);
            size[i] += size[dfn[cur->right->val]];
        }
    }
    vector<int> treeQueries(TreeNode* root, vector<int>& queries) {
        dfnnum = 0;
        f(root, 0);
        for (int i = 1; i <= dfnnum; i++) {
            maxl[i] = max(maxl[i - 1], deep[i]);
        }
        maxl[0] = 0;
        maxl[dfnnum + 1] = 0;
        for (int i = dfnnum; i >= 1; i--)
            maxr[i] = max(maxr[i + 1], deep[i]);
        maxr[0] = 0;
        maxr[dfnnum + 1] = 0;
        int m = queries.size();
        vector<int> ans;
        for (int i = 0; i < m; i++) {
            int leftmax = maxl[dfn[queries[i]] - 1];
            int rightmax = maxr[dfn[queries[i]] + size[dfn[queries[i]]]];
            ans.push_back(max(leftmax, rightmax));
        }
        return ans;
    }
};

 对树做dfn序,然后求个子树的大小和各节点的高度,并且求出从0到各dfn序节点之间的最大高度和从n到各dfn序节点的最大高度,然后挨个求就可以

从树中删除边的最小分数

class Solution {
public:
    static const int maxn = 1005;
    vector<vector<int>> pragh;
    int dfn[maxn];
    int size[maxn];
    int xog[maxn];
    int dfnnum;

    void build(int n) {
        int dfnnum = 0;
        for (int i = 0; i <= n; i++) {
            pragh.push_back(vector<int>());
            dfn[i] = 0;
            size[0] = 0;
            xog[i] = 0;
        }
    }

    void insert(int u, int v) {
        pragh[u].push_back(v);
        pragh[v].push_back(u);
    }

    void f(int cur, vector<int>& nums) {
        int i = ++dfnnum;
        dfn[cur] = i;
        xog[i] = nums[cur];
        size[i] = 1;
        for (auto node : pragh[cur]) {
            if (dfn[node] == 0) {
                f(node, nums);
                xog[i] ^= xog[dfn[node]];
                size[i] += size[dfn[node]];
            }
        }
    }

    int minimumScore(vector<int>& nums, vector<vector<int>>& edges) {
        int n = nums.size();
        build(n);
        for (auto edge : edges) {
            insert(edge[0], edge[1]);
        }
        f(0, nums);
        int ans = INT_MAX;
        for (int i = 0; i < edges.size(); i++) {
            int a = max(dfn[edges[i][0]], dfn[edges[i][1]]);
            for (int j = i + 1; j < edges.size(); j++) {
                int b = max(dfn[edges[j][0]], dfn[edges[j][1]]);
                int pre, pos;
                if (a < b) {
                    pre = a;
                    pos = b;
                } else {
                    pre = b;
                    pos = a;
                }
                int sum1 = xog[pos];
                int sum2, sum3;
                if (pre + size[pre] > pos) {
                    sum2 = sum1 ^ xog[pre];
                    sum3 = xog[pre] ^ xog[1];
                } else {
                    sum2 = xog[pre];
                    sum3 = sum1 ^ sum2 ^ xog[1];
                }
                ans = min(ans, max(sum1, max(sum2, sum3)) -
                                   min(sum1, min(sum2, sum3)));
            }
        }
        return ans;
    }
};

求出各子树的异或和,然后两次for循环枚举各边

  选课

 三维树形dp,枚举最右子树的分配节点个数讨论

#include <iostream>
#include <vector>
using namespace std;
const int maxn = 301;
int nums[maxn];
vector<vector<int>>pragh;//邻接矩阵建图容易找到子树的编号
int n,m;
vector <vector<vector<int>>>dp;
void build() {
	for (int i = 0; i <= n; i++) {
		pragh.push_back(vector<int>());
	}
	dp.assign(n+3, vector<vector<int>>(n+3, vector<int>(m+3,-1)));
}
int f(int i, int j, int k) {//来到节点i,在前j棵子树上,选k门课
	if (k == 0)
		return 0;
	if (j == 0 || k == 1)
		return nums[i];
	if (dp[i][j][k] != -1)
		return dp[i][j][k];
	int ans = f(i, j - 1, k);//不选最右边的子树
	int v = pragh[i][j - 1];
	for (int s = 1; s < k; s++) {
		ans = max(ans, f(i, j - 1, k - s) + f(v, pragh[v].size(), s));
	}
	dp[i][j][k] = ans;
	return ans;
}

int main() {
	cin >> n >> m;
	m++;
	int a, b;
	build();
	for (int i = 1; i <= n; i++) {
		cin >> a >> b;
		pragh[a].push_back(i);
		nums[i] = b;
	}
	nums[0] = 0;
    cout<< f(0, pragh[0].size(), m);
	return 0;
}

 

1.要 i 号点

2.不要 i 号点 

  定义"最优子结构",从大的dfn序到小的dfn序推出转移方程

#include <iostream>
#include <vector>
using namespace std;
const int maxn = 303;
vector<vector<int>>dp;
int nums[maxn];
int head[maxn];
int nex[maxn];
int to[maxn];
int siz[maxn];
int val[maxn];
int n, m;
int cnt;
int dfncnt;
void build() {
	cnt = 1;
	dfncnt = 0;
	for (int i = 0; i <= n; i++) {
		head[i] = 0;
	}
	dp.assign(maxn, vector<int>(maxn, 0));
}
void insert(int u, int v) {
	nex[cnt] = head[u];
	to[cnt] = v;
	head[u] = cnt++;
}
int f(int node) {
	int cur = ++dfncnt;
	val[cur] = nums[node];
	siz[cur] = 1;
	for (int e = head[node]; e > 0; e = nex[e]) {
		int v = to[e];
		siz[cur]+=f(v);
	}
	return siz[cur];
}
int compute() {
	f(0);
	for (int i = n + 1; i >= 2; i--) {
		for (int j = 1; j <= m; j++) {
			dp[i][j] = max(dp[i+siz[i]][j], val[i] + dp[i + 1][j - 1]);
		}
	}
	return dp[2][m];
}
int main() {
	cin >> n >> m;
	build();
	int a, b;
	for (int i = 1; i <= n; i++) {
		cin >> a >> b;
		insert(a, i);
		nums[i] = b;
	}
	cout << compute();
	return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值