第六届蓝桥杯试题--生命之树 解题报告

原题:

在X森林里,上帝创建了生命之树。

他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,代表这个点的和谐值。
上帝要在这棵树内选出一个非空节点集S,使得对于S中的任意两个点a,b,都存在一个点列 {a, v1, v2, ..., vk, b} 使得这个点列中的每个点都是S里面的元素,且序列中相邻两个点间有一条边相连。
在这个前提下,上帝要使得S中的点所对应的整数的和尽量大。
这个最大的和就是上帝给生命之树的评分。

经过atm的努力,他已经知道了上帝给每棵树上每个节点上的整数。但是由于 atm 不擅长计算,他不知道怎样有效的求评分。他需要你为他写一个程序来计算一棵树的分数。

「输入格式」
第一行一个整数 n 表示这棵树有 n 个节点。
第二行 n 个整数,依次表示每个节点的评分。
接下来 n-1 行,每行 2 个整数 u, v,表示存在一条 u 到 v 的边。由于这是一棵树,所以是不存在环的。

「输出格式」
输出一行一个数,表示上帝给这棵树的分数。

「样例输入」
5
1 -2 -3 4 5
4 2
3 1
1 2
2 5

「样例输出」
8
「数据范围」
对于 30% 的数据,n <= 10
对于 100% 的数据,0 < n <= 10^5, 每个节点的评分的绝对值不超过 10^6 。

资源约定:
峰值内存消耗 < 256M

CPU消耗  < 3000ms


解题思路 :

这是一道纯粹的树型动态规划题, 图的结构采用邻接表实现, 整个问题最后化简为"从一棵树中选取一棵结点权值和最大的子树(后面称其为最优子树)". 最优子树的存在形态只有两种: 

1. 包含原树的根结点 ( 直接最优子树 ). 

这种情况下, 原树的直接最优子树必然等于根结点加上它的下属子树中总权非负的直接最优子树.

2. 不包含原树的根结点 ( 间接最优子树 ). 

这种情况下, 原树的间接最优子树必然是它的下属子树中的某一个具有最大权值的直接最优子树或间接最优子树( 谁大取谁 ).

如此一来, 状态转移方程就清晰了, 下面直接代码表示:


#include 
using namespace std;

inline int GetMax( int a, int b){ return a>b?a:b;}
inline int GetMax( int a, int b, int c){ return GetMax(GetMax(a,b),c);}

const int N_Infinite = 0x7fffffff * -1;		// ...负无穷值 
class Vertex;
class Edge{									// ...边 
public:
	Edge *m_next;							// ...出度顶点的下一条边 
	Vertex *m_to;							// ...本边的指向 
	Edge():m_next(NULL),m_to(NULL){}
};

class Vertex{								// ...顶点 
public:
	Edge *m_edge;
	int  m_val,m_maxSon,m_maxAll;			// ...结点权值, 最大子树和(不含根顶点), 最大子树和(含自身根顶点) 
	bool m_block;							// ...阻塞标志 : 为true时表示正在阻塞, 不可遍历 
	Vertex():m_edge(NULL),m_val(0),m_block(false),m_maxSon(N_Infinite),m_maxAll(0){}
	void Add( Vertex *to,Edge* E){			// ...增加一条连接到顶点to的边 
		E->m_next = m_edge;
		E->m_to = to;
		m_edge = E;
	}
};

class Graph{								// ...图 
	public:
		Edge	*ESet;						// ...边集 
		Vertex  *VSet;						// ...顶点集 
		int		ESize;						// ...边的数量 
		Graph( int N ){
			ESet = new Edge[2*N];
			VSet = new Vertex[N+1];
			ESize = 0;
		}
		~Graph(){ delete ESet;delete VSet;}
		void Add( int v1, int v2 ){			// ...在图中的v1与v2顶点间添加一条边 
			VSet[v1].Add(VSet+v2,ESet+ESize++);
			VSet[v2].Add(VSet+v1,ESet+ESize++);
		}
		static void DFS( Vertex *V ){		// ...遍历搜寻以顶点V为根的树的最大子树信息 
			V->m_block = true;				// ...开始阻塞自身 
			V->m_maxAll = V->m_val;
			for( Edge *e = V->m_edge; e!=NULL;e=e->m_next){	// ...遍历所有下属子树 
				if( e->m_to->m_block == false ){
					DFS( e->m_to );
					V->m_maxSon = GetMax( e->m_to->m_maxAll, e->m_to->m_maxSon, V->m_maxSon);
					V->m_maxAll += GetMax( e->m_to->m_maxAll,0);
				}
			}
			V->m_block = false;				// ...解除阻塞 
		}
		int GetVal(){						// ...获取图中的最大子树和 
			DFS( VSet+1 );
			return GetMax( VSet[1].m_maxAll, VSet[1].m_maxSon );
		}
};

int main(int argc, char** argv) 
{
	int N,v1,v2;
	cin >> N;
	Graph G(N);
	for( int i = 1; i <= N; ++i )
		cin >> G.VSet[i].m_val;
	for( int i = 1; i < N; ++i){
		cin >> v1 >> v2;
		G.Add(v1,v2);
	}
	cout << G.GetVal();
	return 0;
}


总结 :上述代码是赛后重新写的, 比赛时为了赶时间, 并没有写得那么多类, 但总体思路是一样的.

你可能感兴趣的:(C++)