@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
数组占 ,题目限制 内存,不进行优化将得到内存超限的结果。
两步优化后可通过 的数据。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD = 1000000007;
int n;
LL m;
inline LL div2(LL x) {
if (x >= MOD) {
x -= MOD;
}
if (x % 2 == 0) {
return x / 2;
}
return (x + MOD) / 2;
}
int main() {
scanf("%d%lld", &n, &m);
for (int i = 1; i < n; ++i) {
LL l = 1;
LL r = m - (n - i);
m = ((m - div2(l + r)) % MOD + MOD) % MOD;
}
printf("%lld\n", m);
return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 5000 + 100;
const LL MOD = 1000000007;
int n, m;
LL ans;
LL inv[maxn];
LL dp[2][maxn], sum[2][maxn];
void initInv() {
inv[1] = 1;
for(int i = 2; i < maxn; ++i) {
inv[i] = (LL)(MOD - MOD / i) * inv[MOD % i] % MOD;
}
}
int main() {
initInv();
scanf("%d%d", &n, &m);
dp[0][0] = 1;
for (int i = 1; i <= n; ++i) {
int nowIndex = i & 1;
int preIndex = nowIndex ^ 1;
sum[preIndex][0] = dp[preIndex][0] * inv[m - (n - i)] % MOD;
for (int j = 1; j < m - (n - i); ++j) {
sum[preIndex][j] = (sum[preIndex][j - 1] + dp[preIndex][j] * inv[m - (n - i) - j] % MOD) % MOD;
}
for (int j = i; j <= m - (n - i); ++j) {
if (i - 1 == 0) {
dp[nowIndex][j] = sum[preIndex][j - 1];
} else {
dp[nowIndex][j] = ((sum[preIndex][j - 1] - sum[preIndex][i - 2]) % MOD + MOD) % MOD;
}
}
}
for (int j = n - 1; j <= m - 1; ++j) {
int preIndex = (n - 1) & 1;
ans = (ans + dp[preIndex][j] * (m - j) % MOD) % MOD;
}
printf("%lld\n", ans);
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int testCases = 50;
ofstream fout;
int getRand(int l, int r) {
return rand() % (r - l + 1) + l;
}
string numToStr(int x) {
string str;
while (x != 0) {
str = char('0' + x % 10) + str;
x /= 10;
}
return str;
}
string getFileName(int index) {
return "data/" + numToStr(index) + ".in";
}
int main() {
srand(time(0));
// data in 10%, 1 <= n <= 2, 1 <= m <= 100
int one = testCases * 10 / 100;
for (int i = 1; i <= one; ++i) {
fout.open(getFileName(i));
switch (getRand(1, 3)) {
case 1:
fout << "1 " << getRand(70, 100) << endl;
break;
case 2:
fout << "2 " << getRand(40, 50) * 2 << endl;
break;
case 3:
fout << "2 " << getRand(40, 50) * 2 - 1 << endl;
break;
}
fout.close();
}
// data in 20%, 1 <= n <= m <= 100
int two = testCases * 20 / 100;
for (int i = one + 1; i <= two; ++i) {
fout.open(getFileName(i));
int n = getRand(10, 70);
int m = getRand(n, 100);
fout << n << " " << m << endl;
fout.close();
}
// data in 100%, 1 <= n <= m <= 5000
int three = testCases;
for (int i = two + 1; i <= three; ++i) {
fout.open(getFileName(i));
int n = getRand(4000, 4700);
int m = getRand(n, 5000);
fout << n << " " << m << endl;
fout.close();
}
return 0;
}