题目传送门
题目描述:
无向连通图 G 有 n 个点,n−1 条边。点从 1 到 n 依次编号,编号为 i 的点的权值为 Wi,每条边的长度均为 1。图上两点 (u,v) 的距离定义为 u 点到 v 点的最短距离。对于图 G 上的点对 (u,v),若它们的距离为 2,则它们之间会产生Wv×Wu 的联合权值。
请问图 G 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?
输入输出格式
输入格式:
第一行包含 1 个整数 n。
接下来 n−1 行,每行包含 2 个用空格隔开的正整数 u,v,表示编号为 u 和编号为 v 的点之间有边相连。
最后 1 行,包含 n 个正整数,每两个正整数之间用一个空格隔开,其中第 i 个整数表示图 G 上编号为 i 的点的权值为 Wi。
输出格式:
输出共 1 行,包含 2 个整数,之间用一个空格隔开,依次为图 G 上联合权值的最大值和所有联合权值之和。由于所有联合权值之和可能很大,输出它时要对10007取余。
分析:因为题目说两个点之间的距离为2就是一个有序数对,所以我们就可以知道一对有序序对之间必定隔着一个中转点 例如下图:
其中1、2、3、4互为序对,因为它们都隔着一个中转点5,它们之间的距离都为2,所以做这道题的基础就是枚举中转点,它延伸出去的点必定互为序对。
那么枚举的问题解决了,这回我们就需要想算的问题。
第一个求最大值很好理解,我们只需要枚举中间点,在枚举它延伸出去的点,然后找一个最大值和次大值就可以了。
例如上图中的最大值就是 c*d=12;
第二个问题需要用到乘法的一些定则。
举个例子:
上图的和为
a
∗
b
+
a
∗
c
+
a
∗
d
+
b
∗
a
+
b
∗
c
+
b
∗
d
+
c
∗
a
+
c
∗
b
+
c
∗
d
+
d
∗
a
+
d
∗
c
+
d
∗
b
a*b+a*c+a*d+b*a+b*c+b*d+c*a+c*b+c*d+d*a+d*c+d*b
a∗b+a∗c+a∗d+b∗a+b∗c+b∗d+c∗a+c∗b+c∗d+d∗a+d∗c+d∗b
经过整理可以变成:
(
a
∗
b
+
a
∗
c
+
a
∗
d
+
b
∗
c
+
b
∗
d
+
c
∗
d
)
∗
2
(a*b+a*c+a*d+b*c+b*d+c*d)*2
(a∗b+a∗c+a∗d+b∗c+b∗d+c∗d)∗2
再经过整理可以变成:
(
a
∗
(
b
+
c
+
d
)
+
b
∗
(
c
+
d
)
+
c
∗
d
)
∗
2
(a*(b+c+d)+b*(c+d)+c*d)*2
(a∗(b+c+d)+b∗(c+d)+c∗d)∗2
所以我们发现求和只需要用一个sum记录前几个数求得的和,然后不停的用后面的数累乘即可,具体的证明可以用乘法分配律展开求得。
不过求得后最后的乘2不能忘掉,这也是一个坑点。
那么具体代码如下:
#include<bits/stdc++.h>
using namespace std;
#define P 10007
struct node{
int next,y;
}e[1000001];
int v[1000001];
int n;
int linkk[10000001];
int len;
void insert(int xx,int yy){
e[++len].next=linkk[xx];
linkk[xx]=len;
e[len].y=yy;
}
int main(){
scanf("%d",&n);
for (int i=1,x,y;i<n;i++) scanf("%d %d",&x,&y),insert(x,y),insert(y,x);
int ans=0,maxx=0;
for (int i=1;i<=n;i++) scanf("%d",&v[i]);
for (int i=1;i<=n;i++){
int max1=0,max2=0,sum=0;
int t=linkk[i];
for (int j=t;j;j=e[j].next){
if (v[e[j].y]>max1) max2=max1,max1=v[e[j].y];
else max2=max(max2,v[e[j].y]);
ans=(ans+sum*v[e[j].y])%P;
sum=(sum+v[e[j].y])%P;
}
maxx=max(maxx,max1*max2);
}
printf("%d %d",maxx,(ans*2)%P);
return 0;
}