@loj - 2023@ 「AHOI / HNOI2017」抛硬币
@description@
小 A 和小 B 是一对好朋友,他们经常一起愉快的玩耍。最近小 B 沉迷于**师手游,天天刷本,根本无心搞学习。但是已经入坑了几个月,却一次都没有抽到 SSR,让他非常怀疑人生。
勤勉的小 A 为了劝说小 B 早日脱坑,认真学习,决定以抛硬币的形式让小 B 明白他是一个彻彻底底的非洲人,从而对这个游戏绝望。两个人同时抛 b 次硬币,如果小 A 的正面朝上的次数大于小 B 正面朝上的次数,则小 A 获胜。
但事实上,小 A 也曾经沉迷过拉拉游戏,而且他一次 UR 也没有抽到过,所以他对于自己的运气也没有太大把握。所以他决定在小 B 没注意的时候作弊,悄悄地多抛几次硬币,当然,为了不让小 B 怀疑,他不会抛太多次。现在小 A 想问你,在多少种可能的情况下,他能够胜过小 B 呢?由于答案可能太大,所以你只需要输出答案在十进制表示下的最后 k 位即可。
@solution@
正着考虑要分类讨论,反着来,总方案数减不合法方案数。直接写式子:
\[2^{a+b} - \sum_{i=0}^{b}{b\choose i}\sum_{j=0}^{i}{a\choose j}
\]
注意到 a - b 相较于 a, b 很小,尝试构造 a - b:
\[\begin{aligned}
\sum_{i=0}^{b}{b\choose i}\sum_{j=0}^{i}{a\choose j} &= \sum_{i=0}^{b}{b\choose i}\sum_{p+q\leq i}{a-b\choose q}{b\choose p} \\
&= \sum_{q=0}^{a-b}{a-b\choose q}\sum_{i=q}^{b}\sum_{i-p\geq q}{b\choose i}{b\choose p}
\end{aligned}
\]
注意到当 q = 0 时,有:
\[\sum_{i=0}^{b}\sum_{i\geq p}{b\choose i}{b\choose p}=\frac{1}{2}[(\sum_{i=0}^{b}{b\choose i})^2 + \sum_{i=0}^{b}{b\choose i}^2]
\]
而众所周知 \(\sum_{i=d}^{b}{b\choose i}{b\choose i-d}=\sum_{i=d}^{b}{b\choose i}{b\choose b-i+d}={2b\choose b+d}\)。那么上式就等于 \(2^{2b-1} + \frac{1}{2}{2b\choose b}\)。
继续做变换:
\[\begin{aligned}
\sum_{i=q}^{b}\sum_{i-p\geq q}{b\choose i}{b\choose p}&=\sum_{i=0}^{b}\sum_{i-p\geq 0}{b\choose i}{b\choose p} - \sum_{d=0}^{q-1}\sum_{i=d}^{b}{b\choose i}{b\choose i-d} \\
&= 2^{2b-1} + \frac{1}{2}{2b\choose b} - \sum_{d=0}^{q-1}{2b\choose b+d}
\end{aligned}
\]
然后就没了。直接上扩展 lucas 定理算组合数。
@accepted code@
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int P[2] = {2, 5}, MOD[2] = {512, 1953125}, INV[2] = {212890625, 787109376};
int pow_mod(int b, ll p, int m) {
int ret = 1;
for(ll i=p;i;i>>=1,b=1LL*b*b%m)
if( i & 1 ) ret = 1LL*ret*b%m;
return ret;
}
int f[2][int(2E6) + 5], ivf[2][int(2E6) + 5];
void init() {
for(int i=0;i<2;i++) {
f[i][0] = 1;
for(int j=1;j<=MOD[i];j++) {
if( j % P[i] ) f[i][j] = 1LL*f[i][j - 1]*j%MOD[i];
else f[i][j] = f[i][j - 1];
}
for(int j=1;j<=MOD[i];j++)
if( 1LL * j * f[i][MOD[i]] % MOD[i] == 1 ) {
ivf[i][MOD[i]] = j;
break;
}
for(int j=MOD[i]-1;j>=0;j--) {
if( (j + 1) % P[i] ) ivf[i][j] = 1LL*ivf[i][j + 1]*(j + 1)%MOD[i];
else ivf[i][j] = ivf[i][j + 1];
}
}
}
int mod;
int comb(ll n, ll m, bool flag = false) {
int ans = 0;
for(int i=0;i<2;i++) {
int x = 1; ll y = 0, z = 0;
for(ll p=n;p;y+=p/P[i],z+=p/MOD[i],p/=P[i])
x = 1LL*x*f[i][p%MOD[i]]%MOD[i];
for(ll p=m;p;y-=p/P[i],z-=p/MOD[i],p/=P[i])
x = 1LL*x*ivf[i][p%MOD[i]]%MOD[i];
for(ll p=n-m;p;y-=p/P[i],z-=p/MOD[i],p/=P[i])
x = 1LL*x*ivf[i][p%MOD[i]]%MOD[i];
if( flag ) {
if( i == 0 ) y--;
else x = 1LL*x*ivf[1][2]%mod;
}
if( z > 0 ) x = 1LL*x*pow_mod(f[i][MOD[i]], z, MOD[i])%MOD[i];
else x = 1LL*x*pow_mod(ivf[i][MOD[i]], -z, MOD[i])%MOD[i];
x = 1LL*x*pow_mod(P[i], y, MOD[i])%MOD[i];
ans = (ans + 1LL*x*INV[i]%mod) % mod;
}
return ans;
}
ll a, b;
int solve() {
int tmp = (pow_mod(2, 2*b - 1, mod) + comb(2*b, b, true)) % mod, d = a - b, ret = 0;
for(int i=0;i<=d&&i<=b;i++) {
int k = comb(d, i);
ret = (ret + 1LL*k*tmp%mod) % mod;
tmp = (tmp + mod - comb(2*b, b + i)) % mod;
}
return (pow_mod(2, a + b, mod) + mod - ret) % mod;
}
void write(int ans, int k) {
if( !k ) return ;
write(ans / 10, k - 1);
putchar(ans % 10 + '0');
}
int main() {
init();
for(int k,i;scanf("%lld%lld%d", &a, &b, &k)==3;) {
for(mod = i = 1; i <= k; i++) mod *= 10;
write(solve(), k), puts("");
}
}
@details@
算是比较中规中矩的组合数学题吧。
主要是我没想到我原来用的扩展 lucas 模板原来是会 TLE 的。。。
可以实现成只剩下 O(1) 个快速幂,而不用每次都去快速幂变成 O(log) 次。。。