题解 基础概率练习题
主要有两种做法
考虑第一个子任务
因为每个位置都是完全等价的所以答案显然是 \(\frac{1}{n}\)
考虑所有 \(\geqslant k\) 的位置,发现如果钦定一些位置 \(\geqslant k\) 的话这些位置也是等价的
所以问题其实可以被转化成钦定 \(\geqslant k\) 的位置,然后计算其它位置都 \(<k\) 的方案数
若有 \(c\) 个位置 \(\geqslant k\) 的话,有
发现 \(c+i\) 相等时后面那个东西都是一样的,所以枚举 \(c+i\)
然后大力变形发现是一个 \(O(n)\) 的式子,于是就可以写了
- 在 \(n\) 很大且只需要求 \(\forall i\in[1, n], \frac{1}{i!}\pmod p\) 时,逆推比正推快太多了
然后题解做法感觉很神奇,所以粘一下题解做法:
加强自 CF1096E。
本来这题是 T2,后来因为 T1 可能比较难写且 T2 验题人给了一个很清新的做法,这题就放到了 T1。下面就说验题人的做法。
考虑条件概率 \(P(A \mid B)=\frac{P(AB)}{P(B)}\) 表示在 B 发生的情况下 A 发生的概率,就等于 AB 同时发生的概率除以 B 发生的概率。这题中 A 是第一个人 rk1,B 是第一个人得分 \(\ge k\)。
那么 \(p_1 = P(B)\) 就是拿第一个人得分 \(\ge k\) 的方案数去除以没有任何限制的方案数。
\(p_2 = P(AB)\) 就是第一个人 rk1 且得分 \(\ge k\) 的概率。我们发现对于每个人,这个概率是相同的,并且只要有人得分 \(\ge k\) 那么就一定会对这个概率产生贡献(rk1 得分一定 \(\ge k\))。
那么 \(n \times p_2\) 就是拿至少一个人的得分 \(\ge k\) 的情况除以没有任何限制的情况,然后就能直接解出 \(p_2\)。
\(\frac{p_2}{p_1}\) 就是答案。时间复杂度 \(O(n + m)\)。
点击查看代码
#pragma GCC optimize(3)
// #pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 20000010
#define ll long long
//#define int long long
char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline int read() {
int ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
int n, m, k;
int lim;
const ll mod=998244353;
ll fac[N], inv[N], inv2[N];
inline ll C(int n, int k) {return n<k?0:fac[n]*inv2[k]%mod*inv2[n-k]%mod;}
inline ll qpow(ll a, ll b) {ll ans=1; for (; b; a=a*a%mod,b>>=1) if (b&1) ans=ans*a%mod; return ans;}
namespace task1{
ll ans, sum;
void solve() {
if (n==1) {puts("1"); return ;}
for (int t=k; t<=m; ++t) {
for (int c=1; c<=n&&t*c<=m; ++c) {
ll tem=0;
for (int i=0; i<=n&&(i+c)*t<=m; ++i)
tem=(tem+(i&1?-1:1)*C(n-c, i)*C(m-c*t-i*t+n-c-1, n-c-1))%mod;
// cout<<"t&c: "<<t<<' '<<c<<' '<<tem<<endl;
ans=(ans+C(n-1, c-1)*tem%mod*inv[c])%mod;
}
}
for (int i=k; i<=m; ++i) sum=(sum+C(m-i+n-2, n-2))%mod;
ans=ans*qpow(sum, mod-2)%mod;
printf("%lld\n", (ans%mod+mod)%mod);
}
}
namespace task{
ll ans, sum;
void solve() {
for (int t=1; t<=n&&t*k<=m; ++t)
ans=(ans+((t+1)&1?-1:1)*fac[m-t*k+n-1]*inv2[m-t*k]%mod*inv2[n-t]%mod*inv2[t])%mod;
// for (int c=1; c<=n; ++c) {
// ll tem=0;
// for (int i=0; i<=n-c; ++i)
// tem=(tem+(i&1?-1:1)*C(n-c, i)*C(m-c*k-i*k+n-1, n-1))%mod;
// ans=(ans+C(n-1, c-1)*inv[c]%mod*tem)%mod;
// }
for (int i=k; i<=m; ++i) sum=(sum+C(m-i+n-2, n-2))%mod;
ans=ans*qpow(sum, mod-2)%mod;
printf("%lld\n", (ans%mod+mod)%mod);
}
}
signed main()
{
freopen("probability.in", "r", stdin);
freopen("probability.out", "w", stdout);
n=read(); m=read(); k=read(); lim=max(n, m)<<1;
if (!k) {printf("%lld\n", qpow(n, mod-2)); return 0;}
fac[0]=fac[1]=1; inv[0]=inv[1]=1; inv2[0]=inv2[1]=1;
for (int i=2; i<=lim; ++i) fac[i]=fac[i-1]*i%mod;
inv2[lim]=qpow(fac[lim], mod-2);
for (int i=lim; i; --i) inv2[i-1]=inv2[i]*i%mod;
// for (int i=2; i<=lim; ++i) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
// for (int i=2; i<=lim; ++i) inv2[i]=inv2[i-1]*inv[i]%mod;
// task1::solve();
task::solve();
return 0;
}