虽然这道题打了“动态规划,动规,dp”和“最近公共祖先,LCA”的标签,但实际上一点关系没有???
题目大意
给出一棵树,求树上距离等于2的有序点对的乘积和和乘积最大值.
题目分析
首先,此题说的比较的隐晦. 所谓的有 n − 1 n-1 n−1条边, n n n个点的无相连通图,其实就是一颗无根树. 换句话说,没有一个环. 所以,要是两个点同于另一个点(称为中转点)相连,则它们不可能再有边相连,即它们之间的距离为2.
也就是说,我们可以枚举每个点作为中转点,然后计算以这个点作为中转点的点对的乘积.
不难想到用排列,但是这样 O ( n 2 ) O(n^2) O(n2)的时间复杂度是不可接受的.
可以这样思考:
假设每个中转点周围有两个点,权值分别为 a , b a,b a,b,则联合权值为 2 a b = ( a + b ) 2 − ( a 2 + b 2 ) 2ab=(a+b)^2-(a^2+b^2) 2ab=(a+b)2−(a2+b2).
若有三个点,权值分别为 a , b , c a,b,c a,b,c,则联合权值为 2 a b + 2 b c + 2 a c = ( a + b + c ) 2 − ( a 2 + b 2 + c 2 ) 2ab+2bc+2ac=(a+b+c)^2-(a^2+b^2+c^2) 2ab+2bc+2ac=(a+b+c)2−(a2+b2+c2).
综上,以某个节点为中转点的联合权值之和等于权值和的平方减去权值的平方和.
为了找到最大的联合权值,只需维护一个中转点周围最大的两个权值 m a x 1 , m a x 2 max1,max2 max1,max2,相乘后与其他判断即可.
其他坑点
1.题目只说了对和取模,没有说对最大值取模
2.注意随时取模
程序实现
#include<bits/stdc++.h>
#define maxn 400010
#define mod 10007
using namespace std;
struct edge{
int v,next;
}e[maxn];
int n,val[maxn];
int tot,head[maxn];
int ans1,ans2;
void add(int u,int v){
e[++tot].v =v;
e[tot].next =head[u];
head[u]=tot;
}
int main(){
scanf("%d",&n);
int a,b;
for(int i=1;i<n;i++){
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
for(int i=1;i<=n;i++){
int max1=0,max2=0;
int t1=0,t2=0;//t1表示和,t2表示平方和
for(int j=head[i];j;j=e[j].next ){
int v=e[j].v ;
if(val[v]>max1)max2=max1,max1=val[v];
else if(val[v]>max2)max2=val[v];//维护max1,max2
t1=(t1+val[v])%mod;
t2=(t2+(val[v]*val[v])%mod)%mod;//随时取模
}
t1=(t1*t1)%mod;
ans1=(ans1+t1-t2+mod)%mod;
if(ans2<max1*max2)ans2=max1*max2;
}
printf("%d %d\n",ans2,ans1);
return 0;
}
题后总结
1.注意题目中的隐藏条件,比如说这题的中转点和平方的妙用. 实际上,这道题利用了树上路径的思想(树上路径是唯一的),平方差的使用要对这方面的数学知识很熟悉才行.
2.注意题目细节,比如说什么取模什么不用.