LuoguP1351[NOIP2014] 联合权值 解题报告【树形DP】

题目描述
无向连通图G有 n 个点,n1条边。点从 1 n依次编号,编号为 i 的点的权值为Wi,每条边的长度均为 1 。图上两点(u,v)的距离定义为 u 点到v点的最短距离。对于图G 上的点对 (u,v) ,若它们的距离为 2 ,则它们之间会产生Wu×Wv的联合权值。
请问图G上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?
输入输出格式
输入格式:
输入文件名为link .in。
第一行包含1 个整数 n
接下来n1行,每行包含 2 个用空格隔开的正整数 u v,表示编号为 u 和编号为v的点之间有边相连。
最后1 行,包含 n 个正整数,每两个正整数之间用一个空格隔开,其中第i个整数表示图G上编号为 i 的点的权值为Wi
输出格式:
输出文件名为link .out 。
输出共1 行,包含2个整数,之间用一个空格隔开,依次为图G上联合权值的最大值
和所有联合权值之和。由于所有联合权值之和可能很大,输出它时要对 10007 取余。
输入输出样例
输入样例#1:
5
1 2
2 3
3 4
4 5
1 5 2 3 10
输出样例#1:
20 74
说明

本例输入的图如上所示,距离为2 的有序点对有 (1,3) (2,4) (3,1) (3,5) (4,2) (5,3)
其联合权值分别为 2 15 2 20 15 20 。其中最大的是20,总和为74。
【数据说明】
对于30% 的数据, 1<n100
对于60% 的数据, 1<n2000
对于100%的数据, 1<n200,000 0<wi10,000
解题报告
我们考虑什么样的点对满足距离等于2。一是一个点和他的爷爷,另一个是一个点的儿子节点中的两个。
我们重点考虑第二种情况的联合权值的计算。
联合权值的最大值一定等于最大点权*次大点权,因此更新最大值和次大值就好了。重点是怎样求和。
所谓联合权值的和,也就是一个点的所有儿子两两间的点权积,我们计算一个点权和: sumson=wson1+wson2+...+wsonk ,再计算一个平方和 summ=w2son1+w2son2+...+w2sonk ,可以得出,这一个点的儿子们的和 sum=sum2sonsumm
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200000,mod=10007;
struct edge
{
    int v,next;
}ed[2*N+5];
int n,father[N+5];
int sum,vmax,w[N+5];
int head[N+5],num;
void build(int u,int v)
{
    ed[++num].v=v;
    ed[num].next=head[u];
    head[u]=num;
}
void dfs(int u,int f)
{
    int max_1=0,max_2=0,sum_son=0,sum_m=0;
    if(father[f])sum=(sum+w[u]*w[father[f]]*2%mod)%mod,vmax=max(vmax,w[u]*w[father[f]]);//之所以*2是因为他问的是点对
    for(int i=head[u];i!=-1;i=ed[i].next)
    {
        int v=ed[i].v;
        if(v==f)continue;
        father[v]=u;
        if(w[v]>max_1)max_1=w[v];
        else if(w[v]>max_2)max_2=w[v];
        sum_son=(sum_son+w[v])%mod;
        sum_m=(sum_m+w[v]*w[v]%mod)%mod;
        dfs(v,u);
    }
    sum=(sum+(sum_son*sum_son%mod-sum_m%mod+mod)%mod)%mod;
    vmax=max(vmax,max_1*max_2);
}
int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        build(u,v);
        build(v,u);
    }
    for(int i=1;i<=n;i++)scanf("%d",&w[i]);
    dfs(1,0);
    printf("%d %d",vmax,sum%mod);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值