P1351 联合权值 noip提高组2014

题目描述

无向连通图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 取余。

输入输出样例

输入样例#1:
5  
1 2  
2 3
3 4  
4 5  
1 5 2 3 10 
输出样例#1:
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 。

这题是去年时候一直做不过去的,后来知道用并查集的方法可以过六十分。今年再看,突然变水了,看数据范围只能用O(n)或者O(n*log(n))的方法过,于是我们可以考虑在这个复杂度范围内的算法,但是这时,根据题意可以发现,只有n-1条边,意味着这是一棵树,于是我们再考虑答案的统计,以节点h为子树的总答案等于h所有子节点总的答案加上h所有孙子节点的和与h权值的乘积(乘法分配率)再加上h的子节点的权值与其他h的子节点的权值的乘积。这样,只要维护几个数组即可,sum数组统计总答案,maxx数组,记录以h为根的儿子结点的最大权值,son记录儿子结点的权值和,于是就A掉了

//此题以前做的时候操之过急,没有考虑周全,现在重新整理一下思路,thinktwice, codeonce
//首先想好要维护哪些数组,第一,sum数组,统计答案(也就是总的联合权值),son数组,记录某个节点的儿子节点的权值之和,
//每次统计时用w[h]*son[v],v是h的儿子节点,但这里还有一种情况,就是两个儿子结点之间的联合权值,观察可以得到
//每搜到一个子节点,就用维护的maxx数组维护已搜到的子节点,用当前子节点的权值与其相乘更新最大值,并用当前子节点的权值
//乘上已搜到的子节点的权值和即为联合权值总数,唯一要担心的恐怕就是递归报站吧
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define For(i,a,b) for(i=(a);i<=(b);++i)
#define rep(i,a,b) for(i=(a);i>=(b);--i)
#define ll long long
#define mm(a,b) memset(a,b,sizeof(a))
#define inf 999999999
#define mod 10007
using namespace std;
int read(){
	int sum = 0,fg = 1;
	char c = getchar();
	while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
	while(c >='0' && c <='9')sum = sum*10 + c-'0',c = getchar();
	return sum * fg;
}
ll readll(){
	ll sum = 0,fg = 1;
	char c = getchar();
	while(c < '0' || c > '9'){if(c == '-')fg = -1;c = getchar();}
	while(c >='0' && c <='9')sum = sum*10 + c-'0',c = getchar();
	return sum * fg;
}
const int maxn = 400010;
const int maxm = 200010;
int n;
int Begin[maxn],to[maxn],Next[maxn],e;
void add(int x,int y){
	to[++e] = y;
	Next[e] = Begin[x];
	Begin[x] = e;
}
int vis[maxm];
ll ans_max ,w[maxm] ,maxx[maxm] ,son[maxm] ,sum[maxm];
void dfs(int h){
	vis[h] = 1;
	int v;
	for(int i = Begin[h] ;i; i = Next[i]){
		if(!vis[v = to[i]]){
			dfs(v);
			sum[h] = (sum[h] + w[v] * son[h])%mod;
			son[h] += w[v];
			ans_max = max(ans_max , maxx[h]*w[v]);
			maxx[h] = max(maxx[h] , w[v]);
			ans_max = max(ans_max , maxx[v]*w[h]);
			sum[h] = (sum[h] + sum[v])%mod;
			sum[h] = (sum[h] + w[h] * son[v])%mod;
		}
	}
}
int main(){
	int i,j;
	n = read();
	For(i,1,n-1){
		int x = read(),y = read();
		add(x,y);add(y,x);
	}
	For(i,1,n){
		w[i] = readll();
	}
	dfs(1);
	printf("%lld %lld\n",ans_max,sum[1]%mod*2%mod);
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值