P4345 [SHOI2015] 超能粒子炮·改 题解---------Lucas定理

题面:

题目
题意概括: T T T 次询问,每次给出 n , k n,k n,k,求 ∑ i = 0 k C n i   %   2333 \sum_{i = 0}^{k} C_{n}^{i} \ \% \ 2333 i=0kCni % 2333 1 ≤ T ≤ 1 0 5 , 1 ≤ n , k ≤ 1 0 18 1\leq T \leq10^5,1\leq n,k \leq 10^{18} 1T1051n,k1018

分析:
       看到 模数是质数 并且组合数的上下标都很大,可以想到 Lucas 定理。我们根据取模后的到的余数对这 k k k 个位置进行分类,然后计算贡献。

       当 k ≥ m o d k \geq mod kmod 时:
       r e s = ∑ u = 0 m o d − 1 ∑ i = 0 ⌊ k + 1 m o d ⌋ − 1 C n u + i × m o d + g e t ( n , ⌊ k + 1 m o d ⌋ , ⌊ k + 1 m o d ⌋ × m o d , k ) \Large res = \sum_{u=0}^{mod-1}\sum_{i = 0}^{\left \lfloor \frac{k + 1}{mod} \right \rfloor - 1} C_{n}^{u + i\times mod} + get(n,\left \lfloor \frac{k + 1}{mod} \right \rfloor , \left \lfloor \frac{k + 1}{mod} \right \rfloor \times mod, k) res=u=0mod1i=0modk+11Cnu+i×mod+get(n,modk+1,modk+1×mod,k)
                    = ∑ u = 0 m o d − 1 C n u × ∑ i = 0 ⌊ k + 1 m o d ⌋ − 1 C n m o d i + g e t ( n , ⌊ k + 1 m o d ⌋ , ⌊ k + 1 m o d ⌋ × m o d , k ) \Large \;\;\;\;\;\;=\sum_{u=0}^{mod-1}C_{n}^{u} \times \sum_{i = 0}^{\left \lfloor \frac{k + 1}{mod} \right \rfloor - 1}C_{\frac{n}{mod}}^{i} + get(n,\left \lfloor \frac{k + 1}{mod} \right \rfloor , \left \lfloor \frac{k + 1}{mod} \right \rfloor \times mod, k) =u=0mod1Cnu×i=0modk+11Cmodni+get(n,modk+1,modk+1×mod,k)

       其中 g e t ( n , c n t , s t , e d ) = ∑ i = s t e d C n i get(n, cnt, st, ed) = \sum_{i = st}^{ed} C_{n}^{i} get(n,cnt,st,ed)=i=stedCni e d − s t + 1 < m o d ed - st + 1< mod edst+1<mod c n t cnt cnt 是为了方便计算传上去的。

       我们发现,计算 ∑ i = 0 ⌊ k + 1 m o d ⌋ − 1 C n m o d i \Large \sum_{i = 0}^{\left \lfloor \frac{k + 1}{mod} \right \rfloor - 1}C_{\frac{n}{mod}}^{i} i=0modk+11Cmodni 又转化成了一个子问题。并且上下界都会除以 m o d mod mod,所以减小的会很快。

       当 k < m o d k < mod k<mod 时:
       r e s = ∑ i = 0 k C n i \Large res = \sum_{i = 0}^{k} C_{n}^{i} res=i=0kCni

       如果按照上面的式子去做,那么时间复杂度是 O ( T × m o d ) O(T \times mod) O(T×mod) 的。
       因为 O ( T × m o d ) O(T \times mod) O(T×mod) 是过不去的。 瓶颈在于第一种情况中枚举 u u u 和计算 g e t get get 函数,以及第二种情况中枚举 i i i。它们的复杂度是 O ( m o d ) O(mod) O(mod) 的。我们考虑优化。

       现在问题转变成了如何快速的求出 ∑ i = 0 k C n i , k < m o d \Large \sum_{i = 0}^{k} C_{n}^{i},k < mod i=0kCnik<mod 以及 快速求出 ∑ i = s t e d C n i , e d − s t + 1 < m o d \Large \sum_{i = st}^{ed} C_{n}^{i}, ed -st + 1 < mod i=stedCni,edst+1<mod

1.

       对于 ∑ i = 0 k C n i \Large \sum_{i = 0}^{k} C_{n}^{i} i=0kCni
       我们考虑用 L u c a s Lucas Lucas 计算的时候, C n i = C n % m o d i % m o d × C n m o d i m o d \Large C_{n}^{i} = C_{n \%mod}^{i\%mod} \times C_{\frac{n}{mod}}^{\frac{i}{mod}} Cni=Cn%modi%mod×Cmodnmodi
       因为 i < m o d i < mod i<mod,所以 C n i = C n % m o d i × C n m o d 0 = C n % m o d i \Large C_{n}^{i} = C_{n \% mod}^{i} \times C_{\frac{n}{mod}}^{0} = C_{n \% mod}^{i} Cni=Cn%modi×Cmodn0=Cn%modi

       我们直接预处理出来 C i j , 0 ≤ j ≤ i < m o d \Large C_{i}^{j}, 0 \leq j \leq i < mod Cij0ji<mod,以及 S i j = ∑ k = 0 j C i k \Large S_{i}^{j} = \sum_{k = 0}^{j} C_{i}^{k} Sij=k=0jCik

       那么 ∑ i = 0 k C n i = S n % m o d m i n ( k , n % m o d ) \Large \sum_{i = 0}^{k} C_{n}^{i} = S_{n \% mod}^{min(k, n \% mod)} i=0kCni=Sn%modmin(k,n%mod) O ( 1 ) O(1) O(1) 调用就可以。


2.

       对于 ∑ i = s t e d C n i , e d − s t + 1 < m o d \Large \sum_{i = st}^{ed} C_{n}^{i}, ed -st + 1 < mod i=stedCni,edst+1<mod
       我们考虑此时 s t st st 一定是 m o d mod mod 的倍数,因为 s t st st e d ed ed 是长度不到 m o d mod mod 的剩余那段。
       有一个性质是 若 i , j < m o d i, j < mod i,j<mod C n i + k × m o d \Large C_{n}^{i + k \times mod} Cni+k×mod C n i \Large C_{n}^{i} Cni 的比值 和 C n j + k × m o d \Large C_{n}^{j + k \times mod} Cnj+k×mod C n j \Large C_{n}^{j} Cnj 的比值相同。 并且这个比值是 C n / m o d k \Large C_{n / mod}^{k} Cn/modk。这个可以用 Lucas 轻松证明。
       那么问题就很简单了,我们求出 ∑ i = 0 e d − s t C n i \Large \sum_{i=0}^{ed-st}C_{n}^{i} i=0edstCni,然后再乘上 C n / m o d k \Large C_{n/mod}^{k} Cn/modk 就好了。前面那个东西就是我们上面讨论的,后面那个可以用 L u c a s Lucas Lucas 定理在 l o g 2 n log_2n log2n 的时间复杂度内求出。

       然后这道题就做完了,时间复杂度是 O ( T × l o g 2 n ) O(T \times log_2n) O(T×log2n)

#include// 找规律  好题啊 
using namespace std;
typedef long long LL;
const LL mod = 2333;
int T;
LL n, k, inv[mod + 10], fac[mod + 10], c[mod + 10][mod + 10];
LL sum[mod + 10][mod + 10];
LL C(int n, int m){
	if(n < m) return 0;
	else return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
LL Pow(LL x, LL y){
	LL res = 1, k = x % mod;
	while(y){
		if(y & 1) res = (res * k) % mod;
		y >>= 1;
		k = (k * k) % mod;
	}
	return res % mod;
}
LL Lucas(LL n, LL m){
	if(n < mod && m < mod) return C(n, m);
	else return C(n % mod, m % mod) * Lucas(n / mod, m / mod) % mod;
}
LL get(LL n, LL k, LL st, LL ed){
   if(st > ed) return 0;
   else{
   	    LL tmp = Lucas(n / mod, k);
   	    return tmp * sum[n % mod][min(ed - st, n % mod)] % mod;
   }
}
LL calc(LL n, LL k){// calc(n, k) = Σc[n][0] + c[n][1] + ... + c[n][k] 
	k = min(k, n);// 取小的
	if(k < mod) return sum[n % mod][min(k, n % mod)];// k < mod   根据Lucas定理可知道,  c[n][i] = c[n % mod][i % mod] * c[n / mod][i / mod] = c[n % mod][i] * c[n / mod][0] = c[n % mod][i]	
	else return (sum[n % mod][n % mod] * calc(n / mod, (k + 1) / mod - 1LL) % mod + get(n, (k + 1) / mod, (k + 1) / mod * mod, k)) % mod;
}
int main(){
	fac[0] = 1LL;
	for(int i = 1; i < mod; i++) fac[i] = fac[i - 1] * (1LL * i) % mod;
	inv[mod - 1] = Pow(fac[mod - 1], mod - 2) % mod;
	for(int i = mod - 2; i >= 0; i--) inv[i] = inv[i + 1] * (1LL * (i + 1)) % mod;
	for(int i = 0; i <= mod - 1; i++){
		for(int j = 0; j <= i; j++){
			c[i][j] = C(i, j);
			if(!j) sum[i][j] = c[i][j] % mod;
			else sum[i][j] = (sum[i][j - 1] + c[i][j]) % mod;
		}
	}
	scanf("%d", &T);
	while(T--){
		scanf("%lld%lld", &n, &k);
		k = min(k, n);
		if(n < mod) printf("%lld\n", sum[n][k]);
		else{// n >= mod
            printf("%lld\n", calc(n, k));
		}
	}
	return 0;
}

你可能感兴趣的:(c++,算法)