【题解&&海亮集训&&dp】 洛谷 P1351 联合权值

本文解析了一道图论算法竞赛题目,通过枚举中转点的方法,计算无向连通图中所有有序点对的联合权值最大值及总和,详细介绍了算法思路和实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

题目描述:
无向连通图 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 ab+ac+ad+ba+bc+bd+ca+cb+cd+da+dc+db
经过整理可以变成:
( 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 ab+ac+ad+bc+bd+cd2
再经过整理可以变成:
( 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)+cd2
所以我们发现求和只需要用一个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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值