【noip 2015】联合权值

题目描述

无向连通图G 有n 个点,n - 1 条边。点从1 到n 依次编号,编号为 i 的点的权值为W i ,每条边的长度均为1 。图上两点( u , v ) 的距离定义为u 点到v 点的最短距离。对于图G 上的点对( u, v) ,若它们的距离为2 ,则它们之间会产生Wu×Wv 的联合权值。

请问图G 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?

输入格式:

第一行包含1 个整数n 。
接下来n - 1 行,每行包含 2 个用空格隔开的正整数u 、v ,表示编号为 u 和编号为v 的点之间有边相连。
最后1 行,包含 n 个正整数,每两个正整数之间用一个空格隔开,其中第 i 个整数表示图G 上编号为i 的点的权值为W i 。

输出格式:

输出共1 行,包含2 个整数,之间用一个空格隔开,依次为图G 上联合权值的最大值和所有联合权值之和。由于所有联合权值之和可能很大,输出它时要对10007 取余。

数据范围:
对于100%的数据,1 < n≤ 200 , 000 ,0 < wi≤ 10, 000

一道图论题。。。

题目给定的是一颗树,所以我一开始的想法是对于一个节点,它的父节点和任意一个子节点都能产生联合权值,而它的每两个子节点又可以产生联合权值。

注意,题目里说是“有序对”,所以我们可以只往后找,最后乘2即可。

显然,最大联合权值就是每个节点的父节点和子节点中最大的两个的乘积取max。而总的联合权值就是每个点的子节点两两相乘再分别于父节点相乘的和。。。

好麻烦的说!

就是!明明子节点和父节点没什么区别,非要划分出来不是自找麻烦吗!

别看到树就dfs就父节点子节点啊喂!(使劲拍拍脑子)

直接遍历每个点,对于每个点,遍历它能一步到达的点,找出两个最大的相乘,看看乘积能否更新Max。

对于总的联合权值,不难推出这样一个式子:

sum=a(b+c+d+e+…)+b(c+d+e+…)+c(d+e+…)…(因为只往后找)

把式子倒过来,每一项就可以看做当前点的权值和它前面所有点的权值和的乘积。

代码如下:

#include
#include
using namespace std;
struct node{
    int to,next;
}mem[400001];
int size=0;
int head[200001]={0};
int sum=0,Max=-1e9;
int w[200001];
void add(int from,int to)
{
    size++;
    mem[size].to=to;
    mem[size].next=head[from];
    head[from]=size;
}
void work(int x)
{
    int Max1=0,Max2=0,bef=0;
    for(int i=head[x];i;i=mem[i].next)
    {
        int v=mem[i].to;
        if(w[v]>Max1)
        {
            Max2=Max1;
            Max1=w[v];
        }
        else if(w[v]>Max2) Max2=w[v];
        sum+=bef*w[v];   //bef是前面的点的权值和
        sum%=10007;
        bef+=w[v];       
        bef%=10007;      //bef也要%10007!!否则前面bef*w[i]可能爆int
    }
    Max=max(Max,Max1*Max2);
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;iint u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
    }
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    for(int i=1;i<=n;i++)
    {
        work(i);
    }
    printf("%d %d",Max,sum*2%10007);
    return 0;
}

你可能感兴趣的:(题解乱讲)