Day 43
kotori 和抽卡(二)

解题思路:这不是"数学期望",而是"二项分布中的概率":n 次抽卡中,恰好有 m 次抽到 R 卡的概率。
思路可以分成三步。
第一步,计算 C(n, m)。
C(n, m) 表示:在 n 次抽卡中,选择哪 m 次抽到 R 卡。
公式是:
C(n,m)=\\frac{n!}{m!(n-m)!}
C(5, 3) = (5 * 4 * 3) / (3 * 2 * 1)
代码中:
for (int i = n; i >= n - m + 1; i--) {
ret *= i;
}
计算的是:
n \\times (n-1) \\times \\cdots \\times (n-m+1)
一共有 m 个数。
然后:
for (int i = m; i >= 2; i--) {
ret /= i;
}
除以:
m!
由于前面没有乘上完整的 n!,所以实际上已经隐含完成了:
\\frac{n!}{(n-m)!m!}=C(n,m)
第二步,计算 m 次抽到 R 卡的概率:
for (int i = 0; i < m; i++) {
ret *= 0.8;
}
也就是:
0.8\^m
第三步,计算剩下 n-m 次没有抽到 R 卡的概率:
for (int i = 0; i < n - m; i++) {
ret *= 0.2;
}
R 卡概率是 0.8,所以非 R 卡概率是:
1-0.8=0.2
因此是:
0.2\^{n-m}
最终公式就是:
\\boxed{C(n,m)\\times 0.8\^m\\times 0.2\^{n-m}}
例如输入:
3 2
表示抽 3 次,恰好抽到 2 张 R 卡。
可能的情况有:
R R 非R
R 非R R
非R R R
一共有:
C(3,2)=3
每种情况的概率是:
0.8\^2\\times0.2
所以总概率:
3\\times0.8\^2\\times0.2=0.384
输出:
0.3840
代码实现:
java
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner in = new Scanner(System.in);
int n = in.nextInt(), m = in.nextInt();
double ans = 1.0;
// 计算 C(n, m)
for(int i = n; i > n - m; i--) ans *= i;
for(int i = m; i > 0; i--) ans /= i;
// m 次抽到 R 卡
for(int i = 0; i < m; i++) ans *= 0.8;
// n - m 次没有抽到 R 卡
for(int i = 0; i < n-m; i++) ans *= 0.2;
System.out.printf("%.4f", ans);
}
}
ruby 和薯条

解法一:排序 + 二分查找
这两个二分其实是在找同一个合法区间的左右边界。
对于当前的 nums[i],我们要找前面的 nums[j],满足:
l <= nums[i] - nums[j] <= r
移项得到:
nums[i] - r <= nums[j] <= nums[i] - l
代码实现:
java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
long l = in.nextLong();
long r = in.nextLong();
long[] nums = new long[n];
for (int i = 0; i < n; i++) {
nums[i] = in.nextLong();
}
Arrays.sort(nums);
long cnt = 0;
for (int i = 1; i < n; i++) {
/*
* l <= nums[i] - nums[j] <= r
*
* 等价于:
* nums[i] - r <= nums[j] <= nums[i] - l
*/
long high = nums[i] - l;
// 找第一个大于 high 的位置
int left = 0;
int right = i;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= high) {
left = mid + 1;
} else {
right = mid;
}
}
int rightBound = left;
long low = nums[i] - r;
// 找第一个大于等于 low 的位置
left = 0;
right = i;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < low) {
left = mid + 1;
} else {
right = mid;
}
}
int leftBound = left;
// 合法下标范围是 [leftBound, rightBound)
cnt += rightBound - leftBound;
}
System.out.println(cnt);
}
}
解法二:前缀和 + 滑动窗口
代码实现:
java
import java.util.*;
public class Main {
public static int n, l, r;
public static int[] arr;
// 找出差值在 [0, x] 区间内一共有多少对
public static long find(int x) {
int left = 0;
int right = 0;
long ret = 0;
while (right < n) {
while (arr[right] - arr[left] > x) {
left++;
}
ret += right - left;
right++;
}
return ret;
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
l = in.nextInt();
r = in.nextInt();
arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = in.nextInt();
}
Arrays.sort(arr);
System.out.println(find(r) - find(l - 1));
}
}
循环汉诺塔

解题思路:
代码实现:
java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int x = 1;
int y = 2;
int MOD = 1000000007;
for (int i = 2; i <= n; i++) {
int xx = x;
int yy = y;
x = (2 * yy + 1) % MOD;
y = ((2 * yy + 2) % MOD + xx) % MOD;
}
System.out.println(x + " " + y);
}
}