1. 程式人生 > >[BZOJ4830][Hnoi2017]拋硬幣(組合數學 + 擴充套件 Lucas 定理)

[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)