洛谷·[SHOI2015]超能粒子炮·改

初见安~这里是传送门:洛谷P4345 [SHOI2015]超能粒子炮·改

题解

推推式子就好。显然要求的是这个:

\large \sum_{i=0}^kC_n^i =\sum_{i=0}^kC_{n \%p}^{i \% p}C_{n/p}^{i/p} =\sum_{i=0}^{p-1}C_{n\%p}^i \sum_{j=0}^{\lfloor\frac{k-i}{p}\rfloor}C_{n/p}^j\\ =\sum_{i=0}^{k \%i}C_{n\%p}^i\sum_{i=0}^{\lfloor\frac{k}{p}\rfloor}C_{n/p}^i+\sum_{i=k\%i+1}^{p-1}C_{n\%p}^i\sum_{i=0}^{\lfloor\frac{k}{p}-1\rfloor}C_{n/p}^i

然后你会了

 

好了说人话。题意求这个,没问题吧

\large \sum_{i=0}^kC_n^i

考虑优化。数学方法应该拆不开了,所以看到组合数用Lucas定理拆开:

\large \sum_{i=0}^kC_{n \%p}^{i \% p}C_{n/p}^{i/p}

可以发现n%p是定值,而i%p又只有p种情况。所以考虑枚举i%p的值以及这个值的出现次数,也就是后面对应的整除情况出现的次数:

\large \sum_{i=0}^{p-1}C_{n\%p}^i \sum_{j=0}^{\lfloor\frac{k-i}{p}\rfloor}C_{n/p}^j

可以发现后面一个sum显然就是我们所求问题的子问题,所以递归求解即可。这样的话复杂度是O(Tplog_p^n),显然跑不过。

尝试把两个sum分开,后面的上界显然只有当i > k \% p时为\small \lfloor\frac{k-i}{p}\rfloor - 1,否则为\small \lfloor\frac{k-i}{p}\rfloor。换言之只有两种情况。

所以再分开这两种情况为:

\large \sum_{i=0}^{k \%i}C_{n\%p}^i\sum_{i=0}^{\lfloor\frac{k}{p}\rfloor}C_{n/p}^i+\sum_{i=k\%i+1}^{p-1}C_{n\%p}^i\sum_{i=0}^{\lfloor\frac{k}{p}-1\rfloor}C_{n/p}^i

显然两部分的前半部分的sum都相当于求C的前缀和,可以预处理优化掉,后半部分递归求解即可。

复杂度大概是O(Tlog_p^n),可以过。

上代码——

 

 

 

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<map>
#define maxn 3005
using namespace std;
typedef long long ll;
const ll mod = 2333;
ll read() {
	ll x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

int T;
ll n, k, C[maxn][maxn], S[maxn][maxn];
ll Lucas(ll n, ll k) {
	if(n < k || k < 0) return 0;//边界情况
	if(!k) return 1; 
	ll ans = 0;
	ans = S[n % mod][k % mod] * Lucas(n / mod, k / mod) % mod;//前一个sum
	if(min(k, mod - 1) > k % mod) //稍微特判一下避免撞边界,下面前缀和差分
		ans = (ans + (S[n % mod][min(k, mod - 1)] - S[n % mod][k % mod] + mod) * Lucas(n / mod, k / mod - 1) % mod) % mod;
	return ans;
}

signed main() {
	for(int i = 0; i < mod; i++) {//预处理,C是组合数S,是前缀和
		C[i][0] = S[i][0] = 1;
		for(int j = 1; j <= i; j++) 
			C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod, 
			S[i][j] = (S[i][j - 1] + C[i][j]) % mod;//因为前缀和可能会超出n%p,所以多处理些
		for(int j = i + 1; j < mod; j++) S[i][j] = S[i][j - 1];
	}
	
	T = read();
	while(T--) {
		n = read(), k = read(); if(k > n) k = n;
		printf("%lld\n", Lucas(n, k));
	}
	return 0;
}

迎评:)
——End——

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值