#P2840. 第3题-小红的木棍
-
3000ms
Tried: 11
Accepted: 5
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 次运算。
代码实现
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;
}
}
题目内容
给定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