联合权值
无向连通图G 有n 个点,n - 1 条边。点从1 到n 依次编号,编号为 i 的点的权值为W i ,每条边的长度均为1 。图上两点( u , v ) 的距离定义为u 点到v 点的最短距离。对于图G 上的点对( u, v) ,若它们的距离为2 ,则它们之间会产生Wu×Wv 的联合权值。
请问图G 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?
输入输出格式
输入格式:
输入文件名为link .in。
第一行包含1 个整数n 。
接下来n - 1 行,每行包含 2 个用空格隔开的正整数u 、v ,表示编号为 u 和编号为v 的点之间有边相连。
最后1 行,包含 n 个正整数,每两个正整数之间用一个空格隔开,其中第 i 个整数表示图G 上编号为i 的点的权值为W i 。
输出格式:
输出文件名为link .out 。
输出共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。
【数据说明】
对于30% 的数据,1 < n≤ 100 ;
对于60% 的数据,1 < n≤ 2000;
对于100%的数据,1 < n≤ 200 , 000 ,0 < wi≤ 10, 000 。
【解析】:
比较巧妙。
开始想到暴力,可是数据范围很大。
这时我们将目光放到每一个点上,它对答案的贡献是什么呢?
对于最大的联合权值,与当前点相连的点中权值最大的和次大的乘积才有可能成为最大值。所以我们在扫每一个点是,扫一遍和它相连的点,找出最大点和次大点,
更新最大值。
那么权值和怎么求呢?如下面的图假设现在我们正在扫与x相连的点。
权值和为:
a*b+a*c+a*d+a*e;
+
b*a+b*d+b*c+b*e;
+
c*a+c*b+c*d+c*e;
.
.
.
我们可以提出a或者b。。。。
就变成:
a*(b+c+d+e) + b*(a+c+d+e) + c*(a+b+d+e)。。。。
我们又发现小括号里的式子很像。。。。正好缺它自己 如 a*(b+c+d+e) 的 b+c+d+e 正好把x周围的点的权值加了一遍,只是少他自己。
那我们把它添上。
就又变成了:(假设a+b+c+d+e=sum)
a*(sum-a)+b*(sum-b)+c*(sum-c)+d*(sum-d)+e*(sum-e);
将上式展开就变成:
a*sum-a^2+b*sum-b^2+c*sum-c^2+d*sum-d^2+e*sum-e^2;
将sum从式子中提出来,将二次方的都移到后面,
就又变成了:
sum*(a+b+c+d+e)-(a^2+b^2+c^2+d^2+e^2)
我再变。。。。
权值和===sum^2-(a^2+b^2+c^2+d^2+e^2)
这样我们在扫每一个点是记录和它相连的点的最大权值结点和次大权值结点和相连结点的和 和相连结点的平方和就可以了。
【代码】
#include<bits/stdc++.h> using namespace std; #define N 300100 #define mod 10007 vector<int>vec[N]; int n,maxn,ans,v[N]; int main(){ scanf("%d",&n); for(int i=1,x,y;i<n;i++){ scanf("%d%d",&x,&y); vec[x].push_back(y); vec[y].push_back(x); } for(int i=1;i<=n;i++){ scanf("%d",&v[i]); } maxn=0; for(int i=1;i<=n;i++){ int tot=0,s=0; int max1=0,max2=0; for(int j=0;j<vec[i].size();j++){ int k=v[vec[i][j]]; s=(s+k*k%mod)%mod; tot=(tot+k%mod)%mod; if(k>max1) max2=max1,max1=k; else if(k>max2) max2=k; } maxn=max(maxn,max1*max2); ans=(ans+(tot*tot-s)%mod)%mod; } printf("%d %d\n",maxn,ans); return 0; }