NOIP2014提高组第二题联合权值

还是先看题吧:

试题描述
 无向连通图 G 有 n 个点,n-1 条边。点从 1 到 n 依次编号,编号为 i 的点的权值为 Wi ,每条边的长度均为 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 的点的权值为 Wi。 
输出
输出共 1 行,包含 2 个整数,之间用一个空格隔开,依次为图 G 上联合权值的最大值和所有联合权值之和。由 于 所 有 联 合 权 值 之 和 可 能 很 大 , 输 出 它 时 要 对 10007 取 余 。
输入示例
5
1 2
2 3
3 4
4 5
1 5 2 3 10 
输出示例
20 74
其他说明
样例说明:距离为 2 的有序点对有(1,3)、(2,4)、(3,1)、(3,5)、(4,2)、(5,3)。其联合权值分别为 2、15、2、20、15、20。其中最大的是 20,总和为 74。
数据范围:对于 100% 的数据,1 < n ≤ 200,000,0 < Wi ≤ 10,000。 

十分郁闷,只过了7个点,剩下3个点莫名其妙在第二个数值上WA了。

还是先说思路吧,首先看到n的范围,肯定就是用邻接表存了(注意无向图要正着反着各存一次),还有题目中所说无相连通图有n个节点,n-1条边,说明这是一棵树。然后就开始求解了,第一问:可以依次枚举,但是这样太慢了。我们可以针对每一个节点,通过邻接表来找到它的所有邻居,然后依次判断他们的点权,对于每一个节点维护两个值,该点邻居中点权的第一大和第二大,扫过一遍之后,我们只需找哪一个点第一大与第二大乘积最大即可求出第一问。下面是第二问:看到第二问大家第一反应肯定是找到该点的所有邻居,然后每两个点一次算一遍点权的乘积,累加起来就是结果,第二反应就是这样是O(n^2)的复杂度,不用想肯定超时,第三反应就是设法想到O(N)或O(N log N)的算法。然后就开始想:对于一个节点p,他的邻居a,b,c,d,e……我们需要算ab+ac+ad+……bc+bd……这样能不能用数学公式来实现呢?由于最近刷学校留的暑假作业(初高中数学衔接读本),里面正好有一个章节就在讲因式分解,其中公式背的很6:(a+b)^2=a^2+b^2+2ab,(a+b+c)^2=a^2+b^2+c^2+2*(ab+ac+bc)……然后就立刻联想到其中的ab+ac+bc不就正是我要求的吗?知道这样,我们针对每一个点,只需把这个点的邻居的平方和以及和的平方维护即可,到时候一相减即可算出,时间复杂度O(N)。

下面是代码:

 1 #include<iostream>
 2 #include<queue>
 3 #include<cmath>
 4 #include<algorithm>
 5 #include<cstring>
 6 #include<conio.h>
 7 using namespace std;
 8 const int maxn=200000+10;
 9 int n,u[maxn],v[maxn],w[maxn],first[2*maxn],next[2*maxn],a,b,MAX,lMAX,ans1,sum1,sum2,ans2;
10 int read() 
11 {
12     int f=1,x=0;
13     char ch=getchar();
14     if(ch=='-') f=-1;
15     while(ch<'0'||ch>'9')
16     {
17         if(ch=='-')f=-1;
18         ch=getchar();
19     }
20     while(ch>='0'&& ch<='9') { x=x*10+ch-'0';  ch=getchar(); }
21     return x*f;
22 }
23 void addEdge(int i,int a,int b)//加边
24 {
25     u[i]=a;v[i]=b;
26     next[i]=first[a];
27     first[a]=i;
28 } 
29 int main()
30 {
31     memset(first,-1,sizeof(first));
32     n=read();
33     for(int i=0;i<n-1;i++)
34     {
35         a=read();b=read();
36         addEdge(2*i,a,b);
37         addEdge(2*i+1,b,a);
38     }
39     for(int i=1;i<=n;i++)w[i]=read();
40     for(int i=1;i<=n;i++)
41     {
42         sum1=sum2=MAX=0;
43         lMAX=-1;
44         for(int j=first[i];j!=-1;j=next[j])
45         {
46             if(w[v[j]]>lMAX)//维护最大值和次最大值
47             {
48                 if(w[v[j]]>MAX)MAX=w[v[j]];
49                 else lMAX=w[v[j]];
50             }
51             sum1=(sum1+w[v[j]])%10007;//维护邻居中点权的和
52             sum2=(sum2+(w[v[j]]%10007*w[v[j]]%10007)%10007)%10007;//维护邻居中点权的平方和
53         }
54         ans1=max(ans1,MAX*lMAX);
55         ans2=(ans2+(((sum1*sum1)%10007-sum2))+10007)%10007; //套公式
56     }
57     printf("%d %d",ans1,ans2);
58     return 0;
59 } 

其中还有一个注意事项,就是代码55行中加了一个10007,因为这些都是取模运算,并且其中还有相减的运算,所以很有可能算成负数,因此我们需要加上一个10007这样就能解决出负数的问题了(其实钱老师在讲食物链的时候提到过),以后一定要牢记这些经验教训。

转载于:https://www.cnblogs.com/FYH-SSGSS/p/5736044.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值