【SSL 2373】【luogu P1351】【DG特长生2020 T4】星座图 / 联合权值

这篇博客介绍了如何解决一个关于树形结构的问题,其中的任务是找到所有两点间距离为2的点对,并计算它们点权的积之和与最大值。博主给出了利用乘法分配律优化的算法,通过枚举节点和维护最大第二大点权,有效地计算了最大值和总和,避免了超时的情况。
摘要由CSDN通过智能技术生成

星座图 / 联合权值

题目链接:SSL 2373 / luogu P1351

题目大意

给你一个树,一对点满足条件,要它们的距离为 2。(边的距离为 1)
然后有点权,求满足的点对中,每对点两个点点权的积的和以及最大值。
和要对 10007 取模。

思路

你看到这个距离为 2 2 2 的很特别,那你考虑怎样就会距离为 2 2 2

那你会发现,从一个点可以连向到若干个点,这若干个点两两之间距离都是 2 2 2
那最大值就很好求了,我们直接枚举每个点,求与它相邻的点中点权最大的那两个,然后乘起来。
然后每个点都这么求一遍,取个最大值就是答案。
(最大值不用取模)

接着就是求和。
那你求每个点相邻的那一堆,然后再把每个加起来。
那每一堆怎么求呢?
两两配对都可以,但是我们枚举两个点就会超时。

那我们考虑乘法分配律一下,对于每个点,它都可以和别的点配对。
设点权和是 s u m sum sum,这个点点权是 x x x,这个点和别的配对的贡献就是 x ( s u m − x ) x(sum-x) x(sumx)
x x 1 + x x 2 + . . . x x n = x ( x 1 + x 2 + . . . + x n ) = x ( s u m − x ) xx_1+xx_2+...xx_n=x(x_1+x_2+...+x_n)=x(sum-x) xx1+xx2+...xxn=x(x1+x2+...+xn)=x(sumx),这里的 x i x_i xi 是不包含 x x x 的其它点,所以和是 s u m − x sum-x sumx

然后因为 a , b a,b a,b 的组合和 b , a b,a b,a 的组合是不同的,所以我们并不用除二。

代码

#include<cstdio>
#include<iostream>
#define mo 10007
#define ll long long

using namespace std;

struct node {
	int to, nxt;
}e[600001];
int n, le[200001], KK;
int x, y;
ll ans, maxn, qz[200001];

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK; 
}

int main() {
//	freopen("link.in", "r", stdin);
//	freopen("link.out", "w", stdout);
	
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		add(x, y);
	}
	for (int i = 1; i <= n; i++) scanf("%lld", &qz[i]);
	
	for (int i = 1; i <= n; i++) {
		ll first = 0, second = 0, sum = 0;
		for (int j = le[i]; j; j = e[j].nxt) {
			if (qz[e[j].to] > first) {//维护最大和第二大
				second = first;
				first = qz[e[j].to];
			}
			else if (qz[e[j].to] > second) {
				second = qz[e[j].to];
			}
			sum = (sum + qz[e[j].to]) % mo;
		}
		maxn = max(maxn, first * second);
		
		for (int j = le[i]; j; j = e[j].nxt) {//用每个点乘剩余点的和,每个点都这么乘一次,就可以搞出两两乘的和结果
			sum = ((sum - qz[e[j].to]) % mo + mo) % mo;
			ans = (ans + (sum * qz[e[j].to]) % mo) % mo;
			sum = (sum + qz[e[j].to]) % mo;
		}
	}
	
	printf("%lld %lld", maxn, ans);
	
	fclose(stdin);
	fclose(stdout);
	
	return 0;
}
这是一道经典的组合数学题目,需要用到组合数的性质。 我们可以先考虑 $n=5$ 的情况。这时,一共有 $2^n=32$ 种可能的抛硬币的结果,其中正面朝上的硬币数为 $0,1,2,3,4,5$ 的情况分别有 $1,5,10,10,5,1$ 种。 接下来,我们考虑 $n$ 的任意情况。可以证明,当 $n$ 为偶数时,正面朝上的硬币数的种数与 $n=5$ 时是相同的;当 $n$ 为奇数时,正面朝上的硬币数的种数比 $n=5$ 时多一种。这是因为当抛硬币的次数为偶数时,正反面的数量是相等的,因此正面朝上的硬币数的种数与 $n=5$ 时相同;当抛硬币的次数为奇数时,正反面的数量不相等,因此正面朝上的硬币数的种数比 $n=5$ 时多一种。 因此,需要分别处理 $n$ 为奇数和偶数的情况。当 $n$ 为偶数时,正面朝上的硬币数的种数与 $n=5$ 时相同,因此答案为: $$ \sum_{i=0}^{n/2} \binom{n}{i} $$ 当 $n$ 为奇数时,正面朝上的硬币数的种数比 $n=5$ 时多一种,因此答案为: $$ \sum_{i=0}^{n/2} \binom{n}{i} + \sum_{i=0}^{n/2} \binom{n}{i+1} $$ 需要注意的是,当 $n$ 为 $0$ 时,只有一种可能的结果,即所有硬币都是反面朝上,因此答案为 $1$。 以下是一份参考代码,可以用于计算答案: ```c++ #include <iostream> #include <cmath> using namespace std; int main() { int n; cin >> n; if (n == 0) { cout << "1" << endl; } else { int ans = pow(2, n); if (n % 2 == 0) { for (int i = 0; i <= n / 2; i++) { ans -= 2 * pow(-1, i) * pow(2, n - i) * (1 << i) / (i + 1); } } else { for (int i = 0; i <= n / 2; i++) { ans -= 2 * pow(-1, i) * pow(2, n - i) * (1 << i) / (i + 1); } for (int i = 0; i <= n / 2; i++) { ans -= 2 * pow(-1, i) * pow(2, n - i - 1) * (1 << i) / (i + 1); } } cout << ans << endl; } return 0; } ``` 代码中使用了数学公式计算答案,其中 $\binom{n}{i}$ 使用了移项后再计算的方式,避免了复杂的组合数计算。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值