标签:break 查找 math 经典 getchar return sdi 保存 lin
这一题是一个很经典的树形dp题目,
从题面中提取信息,我们可以发现每个节点有两种状态,选与不选。
所以,我们的状态第一维就可以先确定下来了,第一维为子树的根(节点编号)。
因为每个节点的贡献只跟它的父亲是否参加有关,所以我们只要保留关键信息——选与不选就行了。
所以,我们的状态第二维也可以确定下来了,保存的分别是根节点不选时最大子树程度和与根节点参加时的最大子树程度和。
形式化表达一下:
设\(x\)的儿子为\(y\),根节点为\(root\)(查找\(root\)可能需要一点处理,这点在代码中有体现)
设\(F[x,0]\)表示以\(x\)为根的子树中不选\(x\)的最大子树程度和
设\(F[x,1]\)表示以\(x\)为根的子树中选\(x\)的最大子树程度和
所以我们可以根据这两个性质推出来状态转移方程:
不选根节点,那么显然选择子树中程度最大的
\(F[x,0] = \sum(\max(F[y][1],F[y][0]))\)
选根节点,那么选择儿子的权值就只能放弃
\(F[x,1] += F[y][0]\)
最后答案很显然就是\(\max(F[root][1],F[root][0])\)
#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
#define MAXN 6010
using namespace std;
inline int read() {//快读
int a = 0,f = 1;char v = getchar();
while (!isdigit(v)) {if (v == '-') f = -1; v = getchar();}
while (isdigit(v)) {a = a * 10 + v - 48;v = getchar();}
return a * f;
}
vector <int> G[MAXN];//vector存边
int a[MAXN],n,f[MAXN][2],root;//f[i][0]表示选择这个节点,f[i][1]表示不选择这个节点
bool v[MAXN];//判断是否是儿子
void dp(int x) {
f[x][0] = a[x],f[x][1] = 0;//每次的初始化,将选择这个节点的值设为这个节点的值,不选择这个节点的值设为0
int p = G[x].size();
for (int i = 0;i < p;++i) {
int y = G[x][i];//遍历出边
dp(y);//对每一个儿子进行dp
f[x][1] += max(f[y][0],f[y][1]);//不选择这个节点,则两种决策都不会受到影响
f[x][0] += f[y][1];//选择这个节点,子节点只能不选
}
}
int main() {
n = read();
for (int i = 1;i <= n;++i) {
a[i] = read();//a[i]存储的是这个节点的值
}
for (int i = 1;i < n;++i) {
int x = read(),y =read();
v[x] = 1;//确定这个节点不是最先的祖先
G[y].push_back(x);//存边
}
for (int i = 1;i <= n;++i) {
if (!v[i]) {
root = i;//找到树根
break;
}
}
dp(root);
printf("%d",max(f[root][0],f[root][1]));//判断哪个才是最大的
return 0;
}
有不清楚的欢迎评论
标签:break 查找 math 经典 getchar return sdi 保存 lin
原文地址:https://www.cnblogs.com/doubeecat/p/10798654.html