结点选择

又花了一个多小时把这道题重新整理了一遍,对于添加一条边的部分不太理解。
对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;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值