@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) 次。。。

posted @ 2020-05-30 17:27  Tiw_Air_OAO  阅读(186)  评论(0编辑  收藏  举报