[BZOJ4830][Hnoi2017]抛硬币(组合数学 + 扩展 Lucas 定理)

Addresss

洛谷 P3726
BZOJ 4830
LOJ #2023

Solution

  • 考虑把第二个(长度为 b b )的硬币序列反转(正面变反面,反面变正面)
  • 这样就变成了第一个硬币序列和第二个硬币序列中正面的硬币个数严格大于 b b
  • 所以答案等于
  • i = b + 1 a + b ( a + b i ) \sum_{i=b+1}^{a+b}\binom{a+b}i
  • 根据组合数公式
  • i = 0 n ( n i ) = 2 n \sum_{i=0}^n\binom ni=2^n
  • ( n m ) = ( n n m ) \binom nm=\binom n{n-m}
  • 可以得出 a = b a=b 时答案等于
  • 2 a + b ( a + b a ) 2 \frac{2^{a+b}-\binom{a+b}a}2
  • a b a-b 是奇数时答案为
  • 2 a + b 1 + i = b + 1 a + b 2 ( a + b i ) 2^{a+b-1}+\sum_{i=b+1}^{\lfloor\frac{a+b}2\rfloor}\binom{a+b}i
  • 否则 a b a-b 为偶数:
  • 2 a + b 1 + ( a + b a + b 2 ) 2 + i = b + 1 a + b 2 1 ( a + b i ) 2^{a+b-1}+\frac{\binom{a+b}{\lfloor\frac{a+b}2\rfloor}}2+\sum_{i=b+1}^{\lfloor\frac{a+b}2\rfloor-1}\binom{a+b}i
  • 可以用扩展 Lucas 定理求组合数
  • 扩展 Lucas 定理链接:我好菜啊
  • 求组合数除以 2 2 时只需要对 2 2 的幂特殊处理即可
  • 注意常数优化

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)

typedef long long ll;

const int N = 2e6 + 5, M = 1e4 + 5;

ll a, b;
int k, k1, k2, ZZQ, fac2[N], fac5[N];

void initalize()
{
	int i;
	fac2[0] = fac5[0] = 1;
	For (i, 1, 511)
		fac2[i] = i & 1 ? 1ll * fac2[i - 1] * i % 512 : fac2[i - 1];
	For (i, 1, 1953124)
		fac5[i] = i % 5 ? 1ll * fac5[i - 1] * i % 1953125 : fac5[i - 1];
}

int qpow(int a, ll b, int ZZQ)
{
	int res = 1;
	while (b)
	{
		if (b & 1) res = 1ll * res * a % ZZQ;
		a = 1ll * a * a % ZZQ;
		b >>= 1;
	}
	return res;
}

void exgcd(int a, int b, int &x, int &y)
{
	if (!b) return (void) (x = 1, y = 0);
	exgcd(b, a % b, y, x);
	y -= a / b * x;
}

int inv(int a, int ZZQ)
{
	int x, y;
	exgcd(a, ZZQ, x, y);
	return (x % ZZQ + ZZQ) % ZZQ;
}

int fac(ll n, int p, int ZZQ)
{
	if (n < p) return p == 2 ? fac2[n] % ZZQ : fac5[n] % ZZQ;
	int res = qpow(p == 2 ? fac2[ZZQ - 1] % ZZQ :
		fac5[ZZQ - 1] % ZZQ, n / ZZQ, ZZQ);
	res = 1ll * res * (p == 2 ? fac2[n % ZZQ] % ZZQ
		: fac5[n % ZZQ] % ZZQ) % ZZQ;
	return 1ll * res * fac(n / p, p, ZZQ) % ZZQ;
}

int comb(ll n, ll m, int p, int ZZQ, bool div2)
{
	ll cntn = 0, cntm = 0, cntnm = 0, tmp = n;
	while (tmp) cntn += (tmp /= p); tmp = m;
	while (tmp) cntm += (tmp /= p); tmp = n - m;
	while (tmp) cntnm += (tmp /= p);
	cntn -= cntm + cntnm;
	if (cntn - div2 >= k) return 0;
	return 1ll * fac(n, p, ZZQ) * inv(fac(m, p, ZZQ), ZZQ) % ZZQ
		* inv(fac(n - m, p, ZZQ), ZZQ) % ZZQ *
		qpow(p, cntn - div2, ZZQ) % ZZQ;
}

int C(ll n, ll m, bool div2)
{
	int s1 = comb(n, m, 2, k1, div2), s2 = comb(n, m, 5, k2, 0);
	if (div2) s2 = 1ll * s2 * inv(2, k2) % k2;
	return (1ll * s1 * k2 % ZZQ * inv(k2, k1) % ZZQ
		+ 1ll * s2 * k1 % ZZQ * inv(k1, k2) % ZZQ) % ZZQ;
}

void work()
{
	int ans; ZZQ = k1 = k2 = 1; ll i;
	For (i, 1, k) ZZQ *= 10, k1 *= 2, k2 *= 5;
	ans = qpow(2, a + b - 1, ZZQ);
	if (a == b) ans = (ans - C(a + b, b, 1) + ZZQ) % ZZQ;
	else if (a - b & 1) For (i, b + 1, a + b >> 1)
		ans = (ans + C(a + b, i, 0)) % ZZQ;
	else
	{
		For (i, b + 1, (a + b >> 1) - 1)
			ans = (ans + C(a + b, i, 0)) % ZZQ;
		ans = (ans + C(a + b, a + b >> 1, 1)) % ZZQ;
	}
	For (i, 1, k)
	{
		ZZQ /= 10;
		putchar(ans / ZZQ % 10 + '0');
	}
	putchar('\n');
}

int main()
{
	initalize();
	while (scanf("%lld%lld%d", &a, &b, &k) != EOF)
		work();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/83959504