@51nod - 1245@ Binomial Coefficients Revenge


@description@

C(M,N) = M! / N! / (M - N)! (组合数)。

给出M和质数p,求C(M,0), C(M,1)......C(M,M)这M + 1个数中,有多少数不是p的倍数,有多少是p的倍数但不是p2的倍数,有多少是p2的倍数但不是p^3的倍数......。

原题传送门。

@solution@

考虑一个阶乘 n! 含多少 p 因子,容易发现含 \(\sum_{i=1}\lfloor\frac{n}{p^i}\rfloor\) 个。

因此,组合数 C(M, N) 含因子 p 共 \(\sum_{i=1}(\lfloor\frac{M}{p^i}\rfloor - \lfloor\frac{N}{p^i}\rfloor - \lfloor\frac{M - N}{p^i}\rfloor)\) 个。

而上式又可以写作 \(\sum_{i=1}[N \mod p^i + (M - N) \mod p^i \geq M \mod p^i]\)。或者更通俗地说,N + (M - N) 在 p 进制下的进位次数。

因此直接从低位开始数位 dp,记录一下上一次有没有进位即可。

@accepted code@

#include <cstdio>
#include <cassert>
#include <algorithm>
using namespace std;

typedef long long ll;

ll dgt[66], M, P; int cnt;
void get_dight() {
	cnt = 0;
	for(ll i=M;i;i/=P)
		dgt[++cnt] = i % P;
}
ll fun(ll x) {
	return max(0LL, min(P - 1, x) - max(0LL, x - P + 1) + 1);
}
ll f[2][66], g[2][66];
void solve() {
	scanf("%lld%lld", &M, &P), get_dight();
	
	f[0][0] = 1, f[1][0] = 0;
	for(int i=1;i<=cnt;i++) {
		for(int j=0;j<i;j++)
			for(int o=0;o<=1;o++)
				g[o][j] = f[o][j], f[o][j] = 0;
		f[0][i] = f[1][i] = 0;
		for(int o=0;o<=1;o++) {
			ll p = dgt[i] - o;
			for(int j=0;j<i;j++) f[0][j] += fun(p)*g[o][j];
			p = dgt[i] + P - o;
			for(int j=0;j<i;j++) f[1][j + 1] += fun(p)*g[o][j];
		}
	}
	
	while( f[0][cnt - 1] == 0 ) cnt--;
	
	ll sum = 0;
	for(int i=0;i<cnt;i++)
		printf("%lld%c", f[0][i], i + 1 == cnt ? '\n' : ' '), sum += f[0][i];
	assert(sum == M + 1);
}

int main() {
	int T; scanf("%d", &T);
	while( T-- ) solve();
}

@details@

51nod 搬题的时候并没有附上原题关于输出格式的说明。事实上本题需要舍去末尾多余的 0。

本题所用的结论其实叫作 “库默尔定理”,不过定理名字也不是很重要。

posted @ 2020-03-09 11:12  Tiw_Air_OAO  阅读(139)  评论(0编辑  收藏  举报