【省选模拟测试23 T1直径】更好的做法

文章介绍了如何计算一棵有n个儿子节点的树的直径数量,通过公式(∑ai)^2-∑ai^2/2来确定,并给出了一种优化的枚举方法,保证正确性和效率,最后提供了一个C++代码实现示例。

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

题目大意和普通做法

省选模拟测试23 T1直径


题解

对于上文中有三个儿子的根节点的树,其直径数量为 a b + b c + c a ab+bc+ca ab+bc+ca。那么对于上文中有 n n n个儿子的根节点的树,其直径数量为多少呢?

每个儿子所在子树中的点与其他儿子所在子树中的点都能组成一次直径,而两个点都能组成一次直径,所以要除以2。设第 i i i个儿子的儿子数量为 a i a_i ai,则直径数量为

( ∑ a i ) 2 − ∑ a i 2 2 \dfrac{(\sum a_i)^2-\sum a_i^2}{2} 2(ai)2ai2

要让 k = ( ∑ a i ) 2 − ∑ a i 2 2 k=\dfrac{(\sum a_i)^2-\sum a_i^2}{2} k=2(ai)2ai2,即 2 k = ( ∑ a i ) 2 − ∑ a i 2 2k=(\sum a_i)^2-\sum a_i^2 2k=(ai)2ai2

根据题意, 1 + ∑ ( a i + 1 ) ≤ 1 0 5 1+\sum (a_i+1)\leq 10^5 1+(ai+1)105,于是我们可以枚举 ∑ a i \sum a_i ai

令所有的 a i = 1 a_i=1 ai=1,那么最多能有 n × n − n n\times n-n n×nn条直径。 5000 × 5000 − 5000 > 5000000 5000\times 5000-5000>5000000 5000×50005000>5000000,这些对于题目来说是绰绰有余的了。

枚举找到第一个 v = ∑ a i v=\sum a_i v=ai,使得 v ∗ v − v ≥ 2 k v*v-v\geq 2k vvv2k

对于多出的部分,我们可以将若干个 a i a_i ai组合起来,达到减少更多的效果。

比如,将两个值为1的 a i a_i ai合并为一个值为2的 a i a_i ai,原来减少贡献为 1 2 + 1 2 = 2 1^2+1^2=2 12+12=2,现在贡献为 2 2 = 4 2^2=4 22=4,增加了 2 2 2

当然,如果合并三个或更多,则减少的也会更多。找到一种方法,使其减少到正好为 2 k 2k 2k即可。

时间复杂度为 O ( n + k ) O(n+k) O(n+k)

这种方法相对于上一种方法来说正确性更高,而且运行速度也相差不大。

code

#include<bits/stdc++.h>
using namespace std;
int n,v,now,hv,tot=0,w,vt,a[5005];
struct node{
	int x,y,z;
}ans[5005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=5000;i++){
		if(i*i-i>=n*2){
			v=i;break;
		}
	}
	now=v*v-v-n*2;hv=v;
	for(int i=v;i>=2;i--){
		while(now>=i*i-i&&hv>=i){
			now-=i*i-i;hv-=i;
			a[++w]=i;
		}
	}
	for(int i=1;i<=w+hv;i++){
		ans[++vt]=(node){1,i+1,10000+(i>w)};
	}
	tot=w+hv+1;
	for(int i=1;i<=w;i++){
		for(int j=1;j<=a[i];j++){
			ans[++vt]=(node){i+1,++tot,1};
		}
	}
	printf("%d\n",tot);
	for(int i=1;i<=vt;i++){
		printf("%d %d %d\n",ans[i].x,ans[i].y,ans[i].z);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值