NOIP 2014 联合权值

 题目大意:

 有一棵树, 求距离为2的点权的乘积的和以及最大值。

 边无权, 所以节点i, j只有两种关系可以有联合权值:

 1. 爷爷与孙子( i( 或j) 的父节点的父节点是j( 或i) )

 2. 兄弟( i, j拥有共同的父节点)

 对于30%的数据可用floyd, 枚举任意的有序点对<i,j>, 距离为2即
有联合权值。

 对于60%的数据就随便选一个节点当根, 然后按照上面的两个条件做就行了。

这道题提供两种方法:

 方法一

 注意到n个点n-1条边, 说明了这是一棵树, 每两个点之间只有一条路径, 因为距离为2,这一条路径必定经过一个点, 而这两个点在这一个点的左右。

 那么思路来了, 我们可以枚举中间的一个点, 然后枚举它所连到的另外两个点, 去最大值和第二大值, 可以用邻接表( vector) 来实现。

 怎么求和最好呢? 假设一个点与a,b,c相连.那么和就是a * b + a * c + b * a + b * c + c *a + c * b = a * (b + c) + b * (a + c) + c * (a + b),可以注意到这是一个对称式, a,b,c是不能化简得, 那么后一项发现是总的和减去前一项, 那么算法就出来了, 设o = a + b + c,那么sum = a * (o - a) + b * (o - b) + c * (o - c), 不过注意到和需要取余, 取余的一个技巧, 凡是结果可能大于mod的数就取余, 此题还有一个坑点就是权值可能就是负数, 那么就要加上mod再取余.

 方法2

 枚举每一个点, 则与其相邻的点互为距离为2的点。 该部分的最大值为点权最大的两个
点的积, 所求的和为所有点的权值之和的平方减去每个点的平方, 这样每条边都被跑了两次, 复杂度为O(n)。

送上AC代码:

#include<cstdio>
const int mo=10007,M=200008;
int cnt,x,y,n,i,ans,tot,w[M],v[M<<1],last[M<<1],head[M<<1];

void add(int x,int y) 
{ 
	v[++cnt]=y,last[cnt]=head[x],head[x]=cnt; 
}

int main()
{
    scanf("%d",&n);
    for (i=1;i<n;i++) 
		scanf("%d%d",&x,&y),add(x,y),add(y,x);
    for (i=1;i<=n;i++) 
		scanf("%d",&w[i]);
    for (i=1;i<=n;i++)
    {
        int sum=0,max1=0,max2=0,j,o;
        for (j=head[i];j;j=last[j])
        {
            o=w[v[j]];
            sum=(sum+o)%mo;
            if (o>max1) max2=max1,max1=o;
            else if (o>max2) max2=o;
            tot=(tot-o*o)%mo;
        }
        tot=(tot+sum*sum)%mo;
        sum=max1*max2;
        if (sum>ans) ans=sum;
    }
    printf("%d %d\n",ans,tot);
    return 0;
}
posted @ 2019-08-30 21:17  颓废の子乃酱  阅读(136)  评论(0编辑  收藏  举报