#P2840. 第3题-小红的木棍
-
3000ms
Tried: 10
Accepted: 4
Difficulty: 7
所属公司 :
阿里
时间 :2025年4月14日-阿里国际(开发岗)
-
算法标签>前缀和
第3题-小红的木棍
解题思路
下面给出一种基于“频数 + 前缀和”的解法,其思路是先统计每种木棍长度的出现次数,由于每根木棍长度在 1~5000 之间,因此我们可以构造大小为 5001 的频数数组 cnt,其中 cnt[i] 表示长度为 i 的木棍数量。接着构造前缀和数组 prefix,使得
prefix[i] = cnt[1] + cnt[2] + … + cnt[i]
考虑任取三个木棍,其能组成三角形(满足三边和性质:取排序后记为 x ≤ y ≤ z,其充要条件为 x + y > z)与否。因为木棍均为正数,所以若满足 x + y ≤ z,则一定不能构成三角形。注意:如果三个中有两个等于 z(最大边)则必有 x + z > z,因为 x>0,因此“不能构成三角形”的情况只有当最大边唯一时,即三个木棍排序后为 x, y, z 且 x, y 均严格小于 z 且满足 x + y ≤ z。
因此我们可以枚举“最大边”的长度 z(取值 1~5000),对于固定 z(要求 cnt[z] > 0),再统计所有满足条件的“较短两根木棍”的组合:(x,y)(要求 1 ≤ x ≤ y < z 且 x + y ≤ z)。
对于固定长度 x,注意当 x > z/2 时,因为 x + x > z,就不可能满足 x + y ≤ z。所以只需要枚举 x 从 1 到 floor(z/2);对于每个 x,令 j 的上界为 jmax = z – x(必有 jmax ≥ x),此时符合条件的 y 值为 [x, jmax],组合数为
- 当 y = x 时,数量为 C(cnt[x],2)
- 当 y > x 时,数量为 cnt[x]∗(sumy=x+1jmaxcnt[y])
对每个 z,这两部分组合的和乘上 cnt[z] 就是“以 z 为最大边”而构成三角形条件不满足的三根木棍的个数。
最后注意:题目要求求“不能构成三角形的概率”,也就是
P=(无效三元组数)/(C(n,3))
结果需要 mod 1e9+7(这里模数取 1000000007 ),求模意义下的除法需要用乘以分母的逆元(利用费马小定理)。
由于 n 的上界较大(最多 200000),而木棍的取值只有 1~5000,上述双重循环遍历(z 从 1 到 5000,内部 x 从 1 到 floor(z/2))大约最多 5000×2500≈12.5×10^6 次运算。
代码实现
Python 代码
MOD = 1000000007
import sys
def main():
input = sys.stdin.readline
n = int(input())
arr = list(map(int, input().split()))
MAX = 5000
# 统计频数,cnt[i]表示木棍长度为i的个数
cnt = [0] * (MAX + 1)
for x in arr:
cnt[x] += 1
# 构造前缀和数组,prefix[i] = cnt[1] + cnt[2] + ... + cnt[i]
prefix = [0] * (MAX + 1)
for i in range(1, MAX + 1):
prefix[i] = prefix[i-1] + cnt[i]
# 预先计算6的逆元
inv6 = pow(6, MOD - 2, MOD)
invalid = 0 # 记录无法构成三角形的组合数
# 枚举最大边 z
for z in range(1, MAX + 1):
if cnt[z] == 0:
continue
pairCount = 0 # 对于当前z,统计所有满足 x + y <= z 且 x,y < z 的组合数
# 枚举较短边 x,从1到 z//2,原因见下面说明:
# 当 x > z/2 时,因为 x+x > z,就不可能有任何 y 满足 x+y<=z
for x in range(1, z // 2 + 1):
jmax = z - x # y 的上界
# 1. 当 y = x 时:组合数为 C(cnt[x], 2)
waysSame = 0
if cnt[x] >= 2:
waysSame = (cnt[x] * (cnt[x] - 1) // 2) % MOD
# 2. 当 y > x 时:y 的取值范围为 [x+1, jmax]
waysDiff = cnt[x] * (prefix[jmax] - prefix[x]) % MOD
pairCount = (pairCount + waysSame + waysDiff) % MOD
# 对于每个 z,将上面的组合数乘以 cnt[z]
invalid = (invalid + pairCount * cnt[z]) % MOD
# 计算总三元组数:C(n, 3)
totalTriples = (n * (n - 1) % MOD * (n - 2)) % MOD
totalTriples = (totalTriples * inv6) % MOD
# 求概率 invalid / totalTriples (模意义下除法即乘以逆元)
result = invalid * pow(totalTriples, MOD - 2, MOD) % MOD
print(result)
if __name__ == '__main__':
main()
Java
import java.io.*;
import java.util.*;
public class Main {
static final long MOD = 1000000007;
public static void main(String[] args) throws Exception {
// 使用 BufferedReader 以提高输入效率
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(System.out);
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
// 木棍长度范围是1~5000
int MAX = 5000;
long[] cnt = new long[MAX + 1];
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++){
int x = Integer.parseInt(st.nextToken());
cnt[x]++;
}
// 构造前缀和数组:prefix[i] = cnt[1] + cnt[2] + ... + cnt[i]
long[] prefix = new long[MAX + 1];
prefix[0] = 0;
for (int i = 1; i <= MAX; i++){
prefix[i] = prefix[i - 1] + cnt[i];
}
// 预先计算逆元,注意模数 MOD 为素数
long inv2 = modPow(2, MOD - 2, MOD); // 2 的逆元(常数 500000004)
long inv6 = modPow(6, MOD - 2, MOD); // 6 的逆元(166666668)
long invalid = 0; // 无效(三根木棍不能组成三角形)的三元组数量
// 枚举最大边 z(注意只有 z 必须出现在木棍中,即 cnt[z] > 0)
for (int z = 1; z <= MAX; z++) {
if (cnt[z] == 0) continue;
long pairCount = 0; // 统计所有从长度 < z 中选出的 (x, y) 满足 x + y <= z 的组合数
// x 的取值只需从 1 到 floor(z/2)
for (int x = 1; x <= z / 2; x++) {
int jmax = z - x; // y 的最大长度(必须满足 x + y <= z)
// 若 jmax < x 则没有符合条件的 y,但当 x <= floor(z/2) 时这一条件一定满足
// 1. 当 y = x 时:数量为 C(cnt[x],2)
long waysSame = 0;
if (cnt[x] >= 2) {
waysSame = (cnt[x] * (cnt[x] - 1)) % MOD;
waysSame = (waysSame * inv2) % MOD;
}
// 2. 当 y > x 时:枚举 y 从 x+1 到 jmax 的个数之和(可以用前缀和快速求和)
long waysDiff = (cnt[x] * ((prefix[jmax] - prefix[x]) % MOD)) % MOD;
long curr = (waysSame + waysDiff) % MOD;
pairCount = (pairCount + curr) % MOD;
}
// 对于每个 z,所有 (x, y) 对与 cnt[z] 中任取1根组成一个无效三角形(x + y <= z)的组合
invalid = (invalid + (pairCount * (cnt[z] % MOD)) % MOD) % MOD;
}
// 总的三元组数:C(n, 3) = n*(n-1)*(n-2)/6
long totalTriples = (((((long)n * (n - 1)) % MOD) * (n - 2)) % MOD * inv6) % MOD;
// 求概率:invalid/totalTriples mod MOD,模意义下使用乘以分母的逆元
long result = (invalid * modPow(totalTriples, MOD - 2, MOD)) % MOD;
out.println(result);
out.flush();
out.close();
}
// 快速幂取模(计算 base^exp mod mod)
static long modPow(long base, long exp, long mod) {
long result = 1;
base %= mod;
while(exp > 0) {
if ((exp & 1) == 1) {
result = (result * base) % mod;
}
base = (base * base) % mod;
exp >>= 1;
}
return result;
}
}
C++ 代码
#include <iostream>
#include <vector>
using namespace std;
const long long MOD = 1000000007;
// 快速幂取模,计算 base^exp mod mod
long long modPow(long long base, long long exp, long long mod) {
long long result = 1;
base %= mod;
while(exp > 0) {
if(exp & 1)
result = (result * base) % mod;
base = (base * base) % mod;
exp >>= 1;
}
return result;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
const int MAX = 5000;
// 统计每个长度的出现次数
vector<long long> cnt(MAX + 1, 0);
for (int i = 0; i < n; i++){
int x;
cin >> x;
cnt[x]++;
}
// 构造前缀和数组
vector<long long> prefix(MAX + 1, 0);
for (int i = 1; i <= MAX; i++){
prefix[i] = prefix[i - 1] + cnt[i];
}
// 预先计算逆元,6的逆元用于计算C(n,3)
long long inv6 = modPow(6, MOD - 2, MOD);
long long invalid = 0;
// 枚举最大边 z
for (int z = 1; z <= MAX; z++) {
if (cnt[z] == 0) continue;
long long pairCount = 0;
// 枚举较短边 x 的取值,只需 x 从 1 到 z/2
for (int x = 1; x <= z / 2; x++) {
int jmax = z - x; // y 的上界
long long waysSame = 0;
if (cnt[x] >= 2) {
waysSame = (cnt[x] * (cnt[x] - 1) / 2) % MOD;
}
long long waysDiff = cnt[x] * ((prefix[jmax] - prefix[x]) % MOD) % MOD;
pairCount = (pairCount + waysSame + waysDiff) % MOD;
}
invalid = (invalid + pairCount * cnt[z]) % MOD;
}
// 总三元组数 C(n, 3)
long long totalTriples = (((long long)n * (n - 1)) % MOD * (n - 2)) % MOD;
totalTriples = (totalTriples * inv6) % MOD;
// 计算最终答案:invalid / totalTriples (模意义下除法)
long long result = (invalid * modPow(totalTriples, MOD - 2, MOD)) % MOD;
cout << result << "\n";
return 0;
}
题目内容
给定n 根木棍,第i根木棍的长度为ai。
小红从中选出任意3根不同木棍,其中长度可以相同,她希望这3根木棍不能组成一个三角形。
现在请你告诉她有多少概率做到,结果对109+7取模。
提示:本题中,在进行除法的取模时,即计算(p×q−1mod M),其中,q−1 可以使用公式(qM−2modM)得到:例如,在计算45mod M时,根据公式4−1=(4M−2mod M)=250 000 002,得到(p×q−1mod M)=5×250 000 002 mod M=250 000 003。
输入描述
第一行一个整数n,表示木棍的数量。
第二行n个整数ai,表示第i根木棍的长度。
3≦n≦2×105,1≦ai≦5000
输出描述
可以证明答案可以表示为一个不可约分数,为了避免精度问题,请直接输出整数(p×q−1mod M)作为答案,其中M=(109+7),q−1是满足q×q−1≡1 (mod M)的整数。
更具体地,你需要找到一个整数x∈[0,M)满足x×q对M取模等于p。
样例1
输入
4
1 1 1 2
输出
750000006
样例2
输入
3
2 3 4
输出
0
样例3
输入
3
2 6 10
输出
1