【NOIP2014提高组】联合权值——从60到100

【问题描述】  
 
  无向连通图 G 有 n 个点,n-1 条边。点从 1 到 n 依次编号,编号为 i 的点的权值为 W_i, 每条边的长度均为 1。图上两点(u, v)的距离定义为 u 点到 v 点的最短距离。对于图 G 上的点对(u, v),若它们的距离为 2,则它们之间会产生(W_u)×(W_v)的联合权值。 

  请问图 G 上所有可产生联合权值的有序点对中,联合权值最大的是多少?所有联合权值之和是多少?   
     
  【输入格式】  
 
  第一行包含1个整数 n。
  接下来n-1行,每行包含2个用空格隔开的正整数u、v,表示编号为u和编号为v的点之间有边相连。
  最后1行,包含n个正整数,每两个正整数之间用一个空格隔开,其中第i 个整数表示 图G上编号为i的点的权值为Wi   。
     
  【输出格式】  
 
  输出共 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。
【题解】

70分方法:

dfs枚举节点1...n,计算以枚举节点为根的深度为2的节点,还可以加一些剪枝

这种方法编程难度不大,但时间复杂度可以达到O(n^2),最多60分,我得了70分

代码(不能满分):

#include<iostream>
#include<cstdio>
#include<cctype>
const int maxn=200005;
const int mo=10007;
using namespace std;
struct edge{
	int to,next;
}e[maxn<<1];
int first[maxn],np=0,w[maxn],n;
int now_p,max_w=0,sum_w=0;
void scan(int& in){
	char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	in=0;
	while(isdigit(ch)) in=in*10+ch-'0',ch=getchar();
}
void addedge(int u,int v){
	e[++np]=(edge){v,first[u]};
	first[u]=np;
}
void init(){
	int x,y;
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		scan(x),scan(y);
		addedge(x,y);
		addedge(y,x);
	} 
	for(int i=1;i<=n;i++) scan(w[i]);
} 
void dfs(int i,int fa,int dep){
	if(dep==2){//搜索到第二层先处理然后返回 
		int t=w[now_p]*w[i];
		if(t>max_w) max_w=t;
		sum_w=(sum_w+t)%mo;
		return;
	}
	for(int p=first[i];p;p=e[p].next){
		int j=e[p].to;
		if(j==fa) return; 
		dfs(j,i,dep+1);
	}
}
int main(){
	init();//建立数据结构	
	for(now_p=1;now_p<=n;now_p++) dfs(now_p,-1,0);
	printf("%d %d\n",max_w,sum_w*2%mo);
	return 0;
}

100分方法:

将枚举深度为二得节点该为枚举节点x所有深度为1的节点,计算每个节点两两相乘即可解决问题2

至于问题1,在x深度为1的节点中找到两个最大的与max_w比较就可以了

现在讨论问题二怎么实现:

1.暴力求和,铁定超时

2.sum1为所有节点权值和,sum2为所有节点权值平方和,它们有什么关系呢,考虑:sum1^2-sum2

如图为sum1*sum1:


显然,每个元素都有一个xi^2(红色),而对xi*xj (i!=j) 有两个(如图,自己思考一下)

所以每个节点两两相乘结果为:sum1^2-sum2

100分代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cctype>
const int maxn=200005;
const int mo=10007;
using namespace std;
struct edge{
	int to,next;
}e[maxn<<1];
int first[maxn],np=0,w[maxn],n;
int now_p,max_w=0;
long long sum_w=0;
void scan(int& in){
	char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	in=0;
	while(isdigit(ch)) in=in*10+ch-'0',ch=getchar();
}
void addedge(int u,int v){
	e[++np]=(edge){v,first[u]};
	first[u]=np;
}
void init(){
	int x,y;
	scan(n);
	for(int i=1;i<n;i++){
		scan(x),scan(y);
		addedge(x,y);
		addedge(y,x);
	} 
	for(int i=1;i<=n;i++) scan(w[i]);
} 
void fake_dfs(int i){
	int node_sum=0,max1=0,max2=0;
	long long sum2=0,sum1=0;
	for(int p=first[i];p;p=e[p].next){
		node_sum++;
		int j=e[p].to;
		sum1+=w[j];
		sum2+=w[j]*w[j];
		if(w[j]>max2){
			if(w[j]>max1) max2=max1,max1=w[j];
			else max2=w[j];
		}
	}
	if(node_sum<=1) return;
	max_w=max(max_w,max1*max2);
	long long t=(sum1*sum1-sum2);//计算 
	sum_w=(t+sum_w)%mo;
}
int main(){
//	freopen("tree.txt","r",stdin);
	init();//建立数据结构	
	for(now_p=1;now_p<=n;now_p++) fake_dfs(now_p);
	printf("%d %lld\n",max_w,sum_w);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/hi_ker/article/details/79761399