题解 P1122 【最大子树和】
对于一棵树,它的最大子树有两种可能:1.根节点被加入;2.根节点未被加入。
对于情况1,设包含某一儿子(但不包含根节点)的最大子树为p,若p的权值之和>0,就把p连接到根节点上;如果p的权值之和<0,则没有必要把p连到根节点上。
当所有儿子被遍历一遍后,统计包含根节点的最大子树R的权值之和sum,如果sum>ans,那么ans=sum;
如果sum<=ans,就说明根节点没有必要加入。最大子树是某一个儿子的子集,其权值和为ans。
最后输出ans就好.
代码如下:
#include<cstdio>
#include<cstring>
#include<vector>
using std::vector;
const int maxn=16000+5;
int n,root_node,ans=0;
bool vis[maxn];//看看哪个结点没有加入到树中
vector<int> M[maxn],son[maxn];//M记录某个结点的相邻结点;son记录某个结点的儿子
int pa[maxn],a[maxn];//pa记录某个结点的父亲,a记录某个结点的权值
void build_tree(const int root_node)//用递归的方法构建一棵树
{
vis[root_node]=1;
for(int i=0;i<M[root_node].size();i++)
{
int p=M[root_node][i];
if(!vis[p])
{
pa[p]=root_node;
son[root_node].push_back(p);
build_tree(p);
}
}
}
int work(const int root_node)//递归求解问题。该函数的意义是,返回包含root_node但不包含root_node父亲的最大子树的权值
{
int i,sum=a[root_node],q;
for(i=0;i<son[root_node].size();i++)
{
q=work(son[root_node][i]);
if(q>0)sum+=q;
}
if(sum>ans)ans=sum;
return sum;
}
int main(int argc,char **argv)
{
int i,x,y,max_node;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",a+i);
if(a[i]>ans)ans=a[i];//如果树中只有一个结点,那么这肯定是权值最大的结点
}
for(i=1;i<n;i++)
{
scanf("%d%d",&x,&y);//输入边
M[x].push_back(y);
M[y].push_back(x);
}
for(i=1;i<=n;i++)
if(max_node>M[i].size())
{
max_node=M[i].size();
root_node=i;//找到一个度最大的结点(通过这种方式尽可能降低树的高度)
}
for(i=1;i<=n;i++)
pa[i]=i;
memset(vis,0,sizeof(vis));
build_tree(root_node);//建立树
work(root_node);//求解
printf("%d\n",ans);
return 0;
}