题目描述:
无向连通图 G G G 有 n n n 个点, n − 1 n−1 n−1 条边。点从 1 1 1 到 n n n 依次编号,编号为 i i i 的点的权值为 W i W_{i} Wi,每条边的长度均为 1 1 1。图上两点 ( u , v ) (u,v) (u,v) 的距离定义为 u u u 点到 v v v 点的最短距离。对于图 G G G 上的点对 ( u , v ) (u,v) (u,v),若它们的距离为 2 2 2,则它们之间会产生 W v × W u W_{v}×W_{u} Wv×Wu 的联合权值。
请问图 G G G上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?
输入格式
第一行包含 1 1 1 个整数 n n n。
接下来 n − 1 n−1 n−1行,每行包含 2 2 2 个用空格隔开的正整数 u , v u,v u,v,表示编号为 u u u 和编号为 v v v 的点之间有边相连。
最后 1 1 1行,包含 n n n 个正整数,每两个正整数之间用一个空格隔开,其中第 i i i 个整数表示图 G G G 上编号为 i i i 的点的权值为 W i W_{i} Wi。
输出格式
输出共 1 1 1 行,包含 2 2 2 个整数,之间用一个空格隔开,依次为图 G G G 上联合权值的最大值和所有联合权值之和。由于所有联合权值之和可能很大,输出它时要对 10007 10007 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 1<n≤100 1<n≤100
对于60%的数据, 1 < n ≤ 2000 1<n≤2000 1<n≤2000
对于100%的数据, 1 < n ≤ 200000 , 0 < W i ≤ 10000 1<n≤200000,0<W_{i}≤10000 1<n≤200000,0<Wi≤10000
保证一定存在可产生联合权值的有序点对。
暴搜解题思路:
- 暴力搜索思路:bfs遍历点的同时访问所有与当前点距离为2的点,只需在这个过程中更新联合权值的最大值以及累加联合权值总和即可。如下图,在遍历 1 1 1的同时访问 4 、 6 、 9 。 4、6、9。 4、6、9。
- 实现思路:编写如下函数get,访问u的所有出边,并累加权值之和以及更新权值的最大值。之后,当bfs遍历图上某点 x x x时,将 x x x的所有出边上的点加入队列的同时调用函数get即可访问这些出边上点的出边。如此就能访问与当前点距离为2的点。详见注释:
/ /访问u的所有出边并更新最大值、累加与x距离为2的权值之和
LL get(LL u,LL x,LL w){
/ /w为x的权值,u是x的出边
LL M=0;
for(int i=head[u];i>=0;i=e[i].next){
int v=e[i].v;
/ /当u的出边v不是x的时候(保证不访问自身)累加权值之和、更新最大值。
if(v!=x) {
M+=(w*W[v])%mod;MAX=max(MAX,w*W[v]);}
}
return M%mod;
}
- 此方法遍历了所有可能的有序对,复杂度极高。
不知道具体有多高,多半要看具体的树结构吧
70分暴搜超时代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef long long LL;
const LL N=2e5+100,mod=10007 ;
struct edge{
int u;
int v;
int next;
}e[2*N];
int head[N],vis[N],cnt=0;
void Insert(int u,int v){
cnt++;
e[cnt].u=u;
e[cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
LL W[N],ANS[N],MAX=0,res=0;
LL get(LL u,LL x,LL w){
LL M=0;
for(int i=head[u];i>=0;i=e[i].next){
int v=e[i].v;
if(v!=x) {
M+=(w*W[v])%mod;MAX=max(MAX,w*W[v]);}
}
return M%mod;
}
void bfs(){
//深度优先遍历
queue<int>q;
q.push(1);
vis[1]=1;
while(!q.empty()){
int u=q.front();
q.pop();
LL M=0;
for(int i=head[u];i>=0;i=e[i].next){
int v=e[i].v;
M+=get(v,u,W[u])%mod;
if(!vis[v]){
q.push(v);vis[v]=1;}
}
res+=M%mod;
}
}
int main(){
memset(head,-1,sizeof(head));
int n;
cin>>n;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
Insert(u,v);
Insert(v,u);
}
for(int i=1;i<=n;i++) cin>>W[i];
bfs();
cout<<MAX<<" "<<res%mod<<endl;
return 0;
}
优化思路:
- 对于某个点 u u u来说,其出边上的点之间的距离为2。如下图, 1 1 1的出边有 2 、 3 、 5 、 7 、 8 2、3、5、7、8 2、3、5、7、8。 2 、 3 、 5 、 7 、 8 之 间 的 距 离 都 为 2 2、3、5、7、8之间的距离都为2 2、3、5、7、8之间的距离都为2
- 优化的思路:bfs遍历图上点 u u u时,先将 u u u的出边上点的权值累加记为 s u m sum sum。记与 u u u相连的点为 v v v,权值为W[v],则与 v v v距离为2的点和 v v v构成的联合权值为W[v]*{sum-W[v]}。由于是有序对,只需在输出答案时乘2即可。最大值也可以在这个过程中更新。详见代码及注释:
满分优化代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef long long LL;
const LL N=2e5+10,mod=10007 ;
struct edge{
int u;
int v;
int next;
}e[2*N];
int head[N],vis[N],cnt=0;
void Insert(int u,int v){
cnt++;
e[cnt].u=u;
e[cnt].v=v;
e[cnt].next=head[u];
head[u]=cnt;
}
LL W[N],MAX=0,res=0;
void bfs(){
queue<int>q;
q.push(1);
vis[1]=1;
while(!q.empty()){
int u=q.front();
q.pop();
LL M=0,sum=0,maxnode;
//访问u的所有出边v,并累加权值记为sum,获取权值最大的节点maxnode,记最大权值为M
for(int i=head[u];i>=0;i=e[i].next){
int v=e[i].v;
W[v]>M ? M=W[v],maxnode=v : M=M;
sum+=W[v];
}
// bfs的同时,累加联合权值之和,更新最大权值。
for(int i=head[u];i>=0;i=e[i].next){
int v=e[i].v;
if(v!=maxnode) MAX=max(MAX,M*W[v]);
res+=((sum-W[v])%mod*W[v]%mod);
if(!vis[v]){
q.push(v);vis[v]=1;}
}
}
}
int main(){
memset(head,-1,sizeof(head));
int n;
cin>>n;
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
Insert(u,v);
Insert(v,u);
}
for(int i=1;i<=n;i++) cin>>W[i];
bfs();
cout<<MAX<<" "<<res%mod<<endl;
return 0;
}