洛谷 P3799 妖梦拼木棒【枚举/组合数学】

这是一篇关于编程竞赛的博客,主要探讨了一道题目,要求使用4根木棒组成一个正三角形。解法涉及到枚举和哈希表,通过两层循环计算不同组合,并对结果取模。文章提到了数据规模和限制条件,并给出了AC代码示例,解释了代码中的关键逻辑和注意事项。

题目背景
上道题中,妖梦斩了一地的木棒,现在她想要将木棒拼起来。

题目描述
nnn 根木棒,现在从中选 444 根,想要组成一个正三角形,问有几种选法?

答案对 109+710^9+7109+7 取模。

输入格式
第一行一个整数 nnn 。第二行 nnn 个整数,第 iii 个整数 aia_iai 代表第 iii 根木棒的长度。

输出格式
一行一个整数代表答案。

输入输出样例
输入 #1

4 
1 1 2 2

输出 #1

1

说明/提示
数据规模与约定

  • 对于 30%30\%30% 的数据,保证 n≤5×103n \le 5 \times 10^3n5×103
  • 对于 100%100\%100% 的数据,保证 1≤n≤1051 \leq n \le 10^51n1050≤ai≤5×1030 \le a_i \le 5 \times 10^30ai5×103

解法 枚举+哈希表+组合

这道题有点意思。由于要用 444 根木棒组成正三角形,就必须要有两根木棒长度相等,剩下的一边,则由 222 根长度之和等于 222 根相等的木棒的长度 的木棒组成。

由于木棒长度 ai≤5000a_i \le 5000ai5000 ,用 O(n2)O(n^2)O(n2) 的算法就能过,于是直接两重循环,暴力枚举上述两种木棒的长度,计算组合方案数并累加。

此处外层循环 cnt[]cnt[]cnt[] 数组,cnt[i]cnt[i]cnt[i] 为长度为 iii 的木棒的个数。要从 cnt[i]cnt[i]cnt[i] 根长度为 iii 的木棒中取出 222 根,即计算组合数 C(cnt[i],2)C(cnt[i], 2)C(cnt[i],2);内层循环中,要从剩余的木棒中取出两根长度之和为 iii 的木棒,令一根长度为 jjj ,另一根长度则为 i−ji - jij ,为避免重复计算,设 j≤i−jj \le i - jjij 。分类讨论:

  1. j==i−jj == i - jj==ij 时,从长度为 jjj 的木棍中取出 222 根合为一条边, 方案数为 C(cnt[j],2)C(cnt[j], 2)C(cnt[j],2)
  2. j≠i−jj \ne i - jj=ij 时,从长度为 jjj 和长度为 i−ji- jij 的木棍中各取出一根,方案数为 C(cnt[j],1)×C(cnt[i−j],1)C(cnt[j], 1) \times C(cnt[i - j], 1)C(cnt[j],1)×C(cnt[ij],1)

将所有外层方案数和内层方案数的乘积汇总,就是总的方案数。不过题目中有些地方描述不清楚, aia_iai 可以为 000 ,那么可以用三根长度一样的木棍加上一根长度为零的木棍,可以组成一个正三角形吗?三根长度为零的木棍,可以组成正三角形吗?尝试提交了几次,从AC代码中发现,这些情况是不允许的:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e3 + 10, mod = 1e9 + 7;
int n, t, cnt[maxn], maxLen = 0, ans = 0;
bool flag = true; //所有长度的木棍都是唯一的 
int C(int n, int k) { //n个数中选出k个数的组合数 
	return (k == 1 ? n : n * (n - 1) / 2) % mod; //k要么为1,要么为2 
}
int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; ++i) {
		scanf("%d", &t);
		++cnt[t];
		if (cnt[t] > 1) flag = false;
		maxLen = max(maxLen, t); //最长的木棍长度 
	}
	if (flag) { printf("0"); return 0; } //所有长度的木棍都是唯一的,无法组成正三角形
	for (int i = 2; i <= maxLen; ++i) { //枚举外层的两根长度为i的木棍组合 
		if (cnt[i] <= 1) continue; //枚举内层的一根木棍长度为j,另一根长度为i-j
		int times = C(cnt[i], 2) % mod; 
		for (int j = 1; j <= i / 2; ++j) { //注意避免重复计算,令j<=i-j 
//			if (j == 0 && cnt[j] >= 1 && cnt[i] >= 3)
//				ans += C(cnt[i], 3) * C(cnt[j], 1);
			if (j == i - j && cnt[j] >= 2)
				ans += times * C(cnt[j], 2) % mod;
			else if (j != i - j && cnt[j] >= 1 && cnt[i - j] >= 1) 
				ans += times * C(cnt[j], 1) * C(cnt[i - j], 1) % mod;
			ans %= mod;
		}
	}
	printf("%d\n", ans);
	return 0;
}

在这里插入图片描述

评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

memcpy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值