题解 P1122 【最大子树和】
Boeing777X · · 题解
这里讲一种我自以为是不同的方法
树形DP、BFS
不同之处就是,我们找这坨树的所有叶子结点,用多源DP反向推上去。
DP思路:
所以说这里很好想的。用f[ i ]表示f[ i ]为根的子树的美观度最大值。那么
f[ i ]=max(0,now[ i ]+{f[ son ]})这里i表示当前结点,
{f[ son ]}:所有的儿子的美观度和,
now[ i ]:当前结点的美观度。之所以与0取最大值,是因为如果以i为根的这坨子树的美观度最大都为负数的 话,那我不如不要。
程序设计:
我们先找叶节点,对f初始化,对于一个叶节点i,如果now[i]<0则f[i]=0,否则f[i]=now[i];
随后将叶节点全部压入队列,对于结点i,如果 f[i] (已经从其叶节点获得了值)+now[i] <0,那么后面的事情不用干了,剪掉这个子树(因为这坨子树美观度是负数),去处理队列中下一个结点,否则找它的父结点(与它相连没访问过的),对于i的父节点j,f[j]+=max(0,f[i]),且将父节点入队(注意不要重复入队)。
另外需要注意对于一个结点i,它的子节点都还没处理完就对i进行处理
处理树的话:
把树建成有向图(边双向,其实就是无向图)叶子节点的出入度必定都是1。这样便找到了叶结点(我觉得这法子挺蠢的)
十分抱歉我不太会用那个好像叫LATEX的东西,凑合着看吧,讲的应该不错(自认为)
code:
#define maxn (16005)*2
using namespace std;
int n,h[maxn],cnt;
int flw[maxn]; //文中now数组
struct node //前向星
{
int to,next;
}edge[maxn];
struct node2 //出入度
{
int id,rd,cd;
}rcd[maxn];
void mktr() //统计出入度
{
for(int i = 1;i <= n;i++)
{
rcd[i].id=i;
for(int j = h[i];j;j=edge[j].next)
{
rcd[edge[j].to].rd++;
rcd[i].cd++;
}
}
}
queue<int>q;
int ind[maxn];
int inz[maxn]; //文中f数组
void bfs()
{
for(int i = 1;i <= n;i++)
{
if(rcd[i].cd==1&&rcd[i].rd==1)
{
q.push(i);
}
}
int now; //当前节点
while(!q.empty())
{
now=q.front(),q.pop();
if(ind[now]!=rcd[now].cd-1)//确定当前的儿子结点已经处理完毕
{
q.push(now);continue;
}
ind[now]=1;
inz[now]=max(inz[now]+flw[now],0);
if(inz[now]<0)
continue;
for(int i = h[now];i;i=edge[i].next)
{
if(ind[edge[i].to]<rcd[edge[i].to].cd-1) //确认目标点访问次数没超(双保险)
{
if(!ind[edge[i].to]) //没入过队
q.push(edge[i].to);
ind[edge[i].to]++;
inz[edge[i].to]+=max(0,inz[now]); //父结点加上当前结点为根的子树的值
}
else
{
inz[edge[i].to]+=max(0,inz[now]); //同上
}
}
}
cout<<inz[now];
}
void add(int x,int y)
{
cnt++;
edge[cnt].to=y;
edge[cnt].next=h[x];
h[x]=cnt;
}
int main()
{
// freopen("testdata.in","r",stdin);
// freopen("testdata.out","w",stdout);
cin>>n;
int x,y;
for(int i = 1;i <= n;i++)
{
scanf("%d",&flw[i]);
}
for(int i = 1;i <= n-1;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
mktr();
bfs();
// cout<<inz[]
return 0;
}