[关闭]
@Dmaxiya 2022-12-04T22:22:31.000000Z 字数 3180 阅读 258

抢红包题解

出题


时,期望值为

时,若 为偶数直接输出 ,为奇数则输出 ,可通过 的数据。


出题人本意是想出一道概率 dp,没想到现场有人写出了更快更好的解法,因此题目难度与出题人预料的不符,根本达不到 hard,这里直接放上现场 AC 的更快的解法,先膜一下 %%%。

更快的解法

由于每个人获得红包的分数等概率地分布在 区间,因此第 个人获得红包的期望就是 ,其中 为到第 个人时剩余的红包分数期望,最后按题意从 模拟推期望即可。出题人的龟速解法不看也罢,可直接跳转至更快的标程,点击此处跳转 AC 提交。

出题人的龟速解法

dp[i][j] 表示前 个人总共领到 分红包的概率,则有:

公式 (2) 表示前 个人共分到 分红包的概率等于前 个人共分到 分红包的概率乘上第 个人能分到 分红包的概率,此时第 个人能分到的红包区间为 ,区间内每个值都是等概率被取到,即取到 分的概率是

公式 (3) 表示当前 个人总共分到 分红包时,最后一个人能被分到红包的分数为 ,再乘上对应的概率 dp[n-1][j] ,求和后就是最后一个人能被分到红包分数的期望。

这种解法的时间复杂度为 ,可通过 的数据。


从公式 (2) 还可以发现求和的每一项在 内是连续的区间,因此可以预处理前缀和将时间复杂度从 降到 ,理论上算法时间复杂度已经可通过 的数据,但还有一些细节需要优化。


最后的模数 是一个质数,且所有除数均不为模数的倍数,可用 复杂度的费马小定理、扩展欧几里得或者 复杂度的线性预处理 以内的逆元,不预处理将在最终的时间复杂度上乘上一个 ,导致超时。

观察递推式发现 dp[i] 的各项值只依赖 dp[i-1],对 dp[j] 没有依赖,可以使用 大小的滚动数组递推,空间复杂度可为 long long 数组占 ,题目限制 内存,不进行优化将得到内存超限的结果。

两步优化后可通过 的数据。

更快的标程

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef long long LL;
  4. const LL MOD = 1000000007;
  5. int n;
  6. LL m;
  7. inline LL div2(LL x) {
  8. if (x >= MOD) {
  9. x -= MOD;
  10. }
  11. if (x % 2 == 0) {
  12. return x / 2;
  13. }
  14. return (x + MOD) / 2;
  15. }
  16. int main() {
  17. scanf("%d%lld", &n, &m);
  18. for (int i = 1; i < n; ++i) {
  19. LL l = 1;
  20. LL r = m - (n - i);
  21. m = ((m - div2(l + r)) % MOD + MOD) % MOD;
  22. }
  23. printf("%lld\n", m);
  24. return 0;
  25. }

出题人的龟速标程

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef long long LL;
  4. const int maxn = 5000 + 100;
  5. const LL MOD = 1000000007;
  6. int n, m;
  7. LL ans;
  8. LL inv[maxn];
  9. LL dp[2][maxn], sum[2][maxn];
  10. void initInv() {
  11. inv[1] = 1;
  12. for(int i = 2; i < maxn; ++i) {
  13. inv[i] = (LL)(MOD - MOD / i) * inv[MOD % i] % MOD;
  14. }
  15. }
  16. int main() {
  17. initInv();
  18. scanf("%d%d", &n, &m);
  19. dp[0][0] = 1;
  20. for (int i = 1; i <= n; ++i) {
  21. int nowIndex = i & 1;
  22. int preIndex = nowIndex ^ 1;
  23. sum[preIndex][0] = dp[preIndex][0] * inv[m - (n - i)] % MOD;
  24. for (int j = 1; j < m - (n - i); ++j) {
  25. sum[preIndex][j] = (sum[preIndex][j - 1] + dp[preIndex][j] * inv[m - (n - i) - j] % MOD) % MOD;
  26. }
  27. for (int j = i; j <= m - (n - i); ++j) {
  28. if (i - 1 == 0) {
  29. dp[nowIndex][j] = sum[preIndex][j - 1];
  30. } else {
  31. dp[nowIndex][j] = ((sum[preIndex][j - 1] - sum[preIndex][i - 2]) % MOD + MOD) % MOD;
  32. }
  33. }
  34. }
  35. for (int j = n - 1; j <= m - 1; ++j) {
  36. int preIndex = (n - 1) & 1;
  37. ans = (ans + dp[preIndex][j] * (m - j) % MOD) % MOD;
  38. }
  39. printf("%lld\n", ans);
  40. return 0;
  41. }

数据生成

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int testCases = 50;
  4. ofstream fout;
  5. int getRand(int l, int r) {
  6. return rand() % (r - l + 1) + l;
  7. }
  8. string numToStr(int x) {
  9. string str;
  10. while (x != 0) {
  11. str = char('0' + x % 10) + str;
  12. x /= 10;
  13. }
  14. return str;
  15. }
  16. string getFileName(int index) {
  17. return "data/" + numToStr(index) + ".in";
  18. }
  19. int main() {
  20. srand(time(0));
  21. // data in 10%, 1 <= n <= 2, 1 <= m <= 100
  22. int one = testCases * 10 / 100;
  23. for (int i = 1; i <= one; ++i) {
  24. fout.open(getFileName(i));
  25. switch (getRand(1, 3)) {
  26. case 1:
  27. fout << "1 " << getRand(70, 100) << endl;
  28. break;
  29. case 2:
  30. fout << "2 " << getRand(40, 50) * 2 << endl;
  31. break;
  32. case 3:
  33. fout << "2 " << getRand(40, 50) * 2 - 1 << endl;
  34. break;
  35. }
  36. fout.close();
  37. }
  38. // data in 20%, 1 <= n <= m <= 100
  39. int two = testCases * 20 / 100;
  40. for (int i = one + 1; i <= two; ++i) {
  41. fout.open(getFileName(i));
  42. int n = getRand(10, 70);
  43. int m = getRand(n, 100);
  44. fout << n << " " << m << endl;
  45. fout.close();
  46. }
  47. // data in 100%, 1 <= n <= m <= 5000
  48. int three = testCases;
  49. for (int i = two + 1; i <= three; ++i) {
  50. fout.open(getFileName(i));
  51. int n = getRand(4000, 4700);
  52. int m = getRand(n, 5000);
  53. fout << n << " " << m << endl;
  54. fout.close();
  55. }
  56. return 0;
  57. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注