HNOI2017 抛硬币(组合数学+扩展卢卡斯)

题目链接

题目大意

给定 a , b a,b ,考虑所有长度为 a a 的01串和长度为 b b 的01串,统计所有前者中的1严格比后者多的方案数。
答案模 1 0 k 10^k
b a 1 0 15 , k 9 , a b 10 , 000 b\le a\le 10^{15},k\le 9,a-b\le 10,000

题解

神仙思路……
a = b a=b 的情况很简单,只需要考虑有多少组“平局”即可,剩下的胜败可以一一对应。
平局显然就是 i = 1 a ( a i ) ( b i ) = ( 2 a a ) \sum_{i=1}^a\binom ai\binom bi=\binom{2a}{a} ,因此答案为 2 2 a ( 2 a a ) 2 \frac{2^{2a}-\binom{2a}{a}}{2}
考虑 a > b a>b ,此时我们会发现,任意一种不胜的情况,把所有位异或1必然对应一种胜的情况。于是此时需要考虑的仅仅是有多少局胜的全部异或1也仍然胜利(神仙!)。
i i 表示 b b 中1的个数, i + j i+j 表示 a a 中1的个数,则需要满足: j > 0 , b i < a i j j>0,b-i<a-i-j ,化简可得 0 < j < a b 0<j<a-b
因此,胜转胜的总方案数如下:
p = i = 0 b j = 1 a b 1 ( a i + j ) ( b i ) = j = 1 a b 1 i = 0 b ( a i + j ) ( b b i ) = j = 1 a b 1 ( a + b j + b ) p=\sum_{i=0}^b\sum_{j=1}^{a-b-1}\binom{a}{i+j}\binom{b}{i}=\sum_{j=1}^{a-b-1}\sum_{i=0}^b\binom{a}{i+j}\binom{b}{b-i}=\sum_{j=1}^{a-b-1}\binom{a+b}{j+b}
于是就可以在 O ( a b ) O(a-b) 的复杂度内计算了。答案就是 2 a + b + p 2 \frac{2^{a+b}+p}{2}
但是这道题很毒瘤,模数不是质数……我们只能用扩展卢卡斯……
更毒瘤的是这道题还卡常,我们算和的时候可以先算出第一个组合数,然后用逆元之类的东西推出后面的,终于AC了……

#include <bits/stdc++.h>
namespace IOStream {
	const int MAXR = 10000000;
	char _READ_[MAXR], _PRINT_[MAXR];
	int _READ_POS_, _PRINT_POS_, _READ_LEN_;
	inline char readc() {
	#ifndef ONLINE_JUDGE
		return getchar();
	#endif
		if (!_READ_POS_) _READ_LEN_ = fread(_READ_, 1, MAXR, stdin);
		char c = _READ_[_READ_POS_++];
		if (_READ_POS_ == MAXR) _READ_POS_ = 0;
		if (_READ_POS_ > _READ_LEN_) return 0;
		return c;
	}
	template<typename T> inline void read(T &x) {
		x = 0; register int flag = 1, c;
		while (((c = readc()) < '0' || c > '9') && c != '-');
		if (c == '-') flag = -1; else x = c - '0';
		while ((c = readc()) >= '0' && c <= '9') x = x * 10 - '0' + c;
		x *= flag;
	}
	template<typename T1, typename ...T2> inline void read(T1 &a, T2&... x) {
		read(a), read(x...);
	}
	inline int reads(char *s) {
		register int len = 0, c;
		while (isspace(c = readc()) || !c);
		s[len++] = c;
		while (!isspace(c = readc()) && c) s[len++] = c;
		s[len] = 0;
		return len;
	}
	inline void ioflush() { fwrite(_PRINT_, 1, _PRINT_POS_, stdout), _PRINT_POS_ = 0; fflush(stdout); }
	inline void printc(char c) {
		_PRINT_[_PRINT_POS_++] = c;
		if (_PRINT_POS_ == MAXR) ioflush();
	}
	inline void prints(char *s) {
		for (int i = 0; s[i]; i++) printc(s[i]);
	}
	template<typename T> inline void print(T x, char c = '\n') {
		if (x < 0) printc('-'), x = -x;
		if (x) {
			static char sta[20];
			register int tp = 0;
			for (; x; x /= 10) sta[tp++] = x % 10 + '0';
			while (tp > 0) printc(sta[--tp]);
		} else printc('0');
		printc(c);
	}
	template<typename T1, typename ...T2> inline void print(T1 x, T2... y) {
		print(x, ' '), print(y...);
	}
}
using namespace IOStream;
using namespace std;
typedef long long ll;
typedef pair<int, int> P;

const int MAXN = 2000005, MAXM = 10005;
ll pf2[MAXM], pf5[MAXN], aa[MAXM], bb[MAXM];
void extgcd(ll a, ll b, ll &x, ll &y) {
	if (b > 0) {
		extgcd(b, a % b, y, x);
		y -= a / b * x;
	} else x = 1, y = 0;
}
ll modpow(ll a, ll b, int c) {
	ll res = 1;
	for (; b; b >>= 1) {
		if (b & 1) res = res * a % c;
		a = a * a % c;
	}
	return res;
}
int get_inv(int x, int p) {
	ll v, y;
	extgcd(x, p, v, y);
	return v;
}
int calc_fact(ll *pf, ll n, int p, int q) {
	ll res = 1, cnt = 0;
	for (; n; n /= q) {
		res = res * pf[n % p] % p;
		if (n >= p) cnt += n / p;
	}
	return res * modpow(pf[p], cnt, p) % p;
}
ll calc_cnt(ll n, int p) {
	ll res = 0;
	for (n /= p; n; n /= p) res += n;
	return res;
}
int calc_binom(ll *pf, ll n, ll m, int p, int pk) {
	if (m > n) return 0;
	int a = calc_fact(pf, n, pk, p), b = calc_fact(pf, m, pk, p), c = calc_fact(pf, n - m, pk, p);
	ll t = calc_cnt(n, p) - calc_cnt(m, p) - calc_cnt(n - m, p);
	return (ll)a * get_inv(b, pk) % pk * get_inv(c, pk) % pk * modpow(p, t, pk) % pk;
}
ll solve(ll *pf, ll a, ll b, int p, int pk) {
	if (a == b + 1) return 0;
	int ta = calc_fact(pf, a + b, pk, p), tb = calc_fact(pf, a - 1, pk, p), tc = calc_fact(pf, b + 1, pk, p);
	ll cnt = calc_cnt(a + b, p) - calc_cnt(a - 1, p) - calc_cnt(b + 1, p);
	ll t = (ll)ta * get_inv(tb, pk) % pk * get_inv(tc, pk) % pk, res = t * modpow(p, cnt, pk) % pk;
	for (int i = 2; i < a - b; i++) {
		ll x = a - i + 1, y = b + i;
		for (; x % p == 0; x /= p) ++cnt;
		for (; y % p == 0; y /= p) --cnt;
		t = t * (x % pk) % pk * get_inv(y % pk, pk) % pk;
		res += t * modpow(p, cnt, pk) % pk;
	}
	return (res % pk + pk) % pk;
}
int main() {
	ll a, b; int k;
	while (~scanf("%lld%lld%d", &a, &b, &k)) {
		int p2 = modpow(2, k + 1, 1000000000), p5 = modpow(5, k, 1000000000), pp = p2 * p5;
		for (int i = pf2[0] = 1; i <= p2; i++) {
			pf2[i] = pf2[i - 1];
			if (i % 2) pf2[i] = pf2[i] * i % p2;
		}
		for (int i = pf5[0] = 1; i <= p5; i++) {
			pf5[i] = pf5[i - 1];
			if (i % 5) pf5[i] = pf5[i] * i % p5;
		}
		int inv1 = get_inv(p5, p2), inv2 = get_inv(p2, p5);
		char prt[10] = "%00lld\n"; prt[2] = '0' + k;
		if (a == b) {
			ll x = calc_binom(pf2, a + b, b, 2, p2);
			ll y = calc_binom(pf5, a + b, b, 5, p5);
			x = inv1 * x % p2 * p5;
			y = inv2 * y % p5 * p2;
			x = ((x + y) % pp + pp) % pp;
			printf(prt, (modpow(2, a + b - 1, pp) - x / 2 + pp) % (pp / 2));
		} else {
			ll x = solve(pf2, a, b, 2, p2), y = solve(pf5, a, b, 5, p5);
			x = inv1 * x % p2 * p5, y = inv2 * y % p5 * p2;
			x = ((x + y) % pp + pp) % pp;
			printf(prt, (modpow(2, a + b - 1, pp) + x / 2) % (pp / 2));
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/WAautomaton/article/details/87869340