又花了一个多小时把这道题重新整理了一遍,对于添加一条边的部分不太理解。
对dfs函数中的 int i=head[x],v; v=edge[i].v; 语句不太理解
题目:
问题描述
有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?
输入格式
第一行包含一个整数 n 。
接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。
接下来一共 n-1 行,每行描述树上的一条边。
输出格式
输出一个整数,代表选出的点的权值和的最大值。
样例输入
5
1 2 3 4 5
1 2
1 3
2 4
2 5
样例输出
12
样例说明
选择3、4、5号点,权值和为 3+4+5 = 12 。
数据规模与约定
对于20%的数据, n <= 20。
对于50%的数据, n <= 1000。
对于100%的数据, n <= 100000。
权值均为不超过1000的正整数。
看过这道题,我们大概可以知道,这是一道和树有关的问题,那么我们需要考虑的就是如何去遍历树?很明显,我们需要用到DFS。仔细读这道题,然后我们就可以得到这还需要用到动态规划,由此,我们做这道题,解题方案也就是树形动态规划了!
既然要用动态规划,构造状态转移方程是必不可少了!
对于叶子结点:
dp[k][0] = 0;
dp[k][1] = k点权值;
对于非叶子结点:
dp[i][0] = max(dp[j][0], dp[j][1]); (j是i的儿子)
dp[i][1] = i点权值 + dp[j][0]; (j是i的儿子)
最大权值即为:
max(dp[0][0], dp[0][1])。(要么不包括根结点,要么包括根结点)
#include<stdio.h>
#include<string.h>
#include<iostream>
#define _Max 10010
#define max(a,b) a>b?a:b;
using namespace std;
struct point {
int v,next; //v指向这条边的另一个结点(父结点),next指向子结点
}edge[_Max*2]; //一条边记录两次,分别以一个点做记录
int head[_Max *2];
int M=0;
int dp[_Max][2];
void addEdge(int from,int to) {
//from结点
edge[M].v=to;
edge[M].next=head[from]; //为-1则定位叶结点,否则,指向另外一条边
head[from]=M++; //指向他的一条边,增加结点
//to结点
edge[M].v=from;
edge[M].next=head[to]; //为-1则定位叶结点,否则,指向另外一条边
head[to]=M++; //指向他的一条边,增加结点
return;
}
void dfs(int x,int pre) {
int i=head[x],v;
for(;i!=-1;i=edge[i].next) //i != -1说明有子结点,则遍历子结点,否则为叶子结点
{
v=edge[i].v;
if(pre==v) { //如果指向的子结点和父结点重合,则说明这个结点是叶子结点,不需要进一步dp
continue;
}
dfs(v,x); //x可以理解为父结点
//深度遍历到最里面的叶子结点的父结点
//如果父结点选择,则子结点不选择,否则子结点可能选择或者不选择,需要要比较两者
dp[x][1]+=dp[v][0];
dp[x][0]+=max(dp[v][0],dp[v][1]);
}
return;
}
int main()
{
int i,n,s,t,tmp;
cin>>n;
memset(head, -1, sizeof(head)); //初始化每个结点都是独立的没有子结点
memset(dp, 0, sizeof(dp));
for(int i=1;i<=n;i++) {
cin>>dp[i][1]; //输入权值,并且记录在dp[i][1]上,i表示第i个结点,1代表取了这个结点
}
for(int i=1;i<n;i++) { //输入边,并且添加edge,一个边添加两个edge
cin>>s>>t;
addEdge(s,t);
}
dfs(1,-1); //深度优先遍历,从第一个结点开始遍历
tmp=max(dp[1][0],dp[1][1]); //求出最大的权值和
printf("%d\n", tmp);
return 0;
}
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define max(a,b) a>b?a:b
const int MAXN = 100001;
int M; //表示边的索引号,初始为0
int head[MAXN]; //表示某个结点所连接的边
int dp[MAXN][2]; //dp[x][0]表示第x个结点不选择时最大权值,dp[x][1]表示第x个结点选择时最大权值
struct Edge{
int toNode; //表示这条边到达的结点
int nextEdge; //表示这条边的出发结点连接的下一条边
}edge[2*MAXN]; //一共有n个结点,有n-1条边,但是不同的出发结点算作不同的边,所以有2n-2条边
//把新边加入边集,构造树
void add(int from, int to){
edge[M].toNode = to;
edge[M].nextEdge = head[from];
head[from] = M++; //head[x]的值可能会被二次赋值
}
//类似dfs遍历
void dfs(int node, int preNode){
for (int i = head[node]; i != -1; i = edge[i].nextEdge){
if (edge[i].toNode == preNode) //说明这条边已经搜索过
continue;
int toNode = edge[i].toNode; //表示边i到达的结点
dfs(toNode, node);
dp[node][0] += max(dp[toNode][0], dp[toNode][1]); //该结点不算,则该边上的另一结点可选也可不选
dp[node][1] += dp[toNode][0]; //改结点选了,该边上另一结点就不能选了
}
}
int main(){
int n;
memset(head, -1, sizeof(head)); //所有边置为-1,表示不存在该边
memset(dp, 0, sizeof(dp));
cin >> n;
for (int i = 1; i <= n; i++){
cin >> dp[i][1]; //每一个结点的权值
}
for (int j = 1; j <= n - 1; j++){
int from, to;
cin >> from >> to;
add(from, to);
add(to, from);
}
dfs(1, 0); //从1号结点开始向后动态规划
int result = max(dp[1][0], dp[1][1]); //因为不确定根结点,所以从几号开始动态规划就找几号的状态
//同样这里也可以写成 dfs(2, 0); int result = max(dp[2][0], dp[2][1]);不过当只有一个结点的时候就不对了
cout << result << endl;
return 0;
}