【刷题笔记】P8688 [蓝桥杯 2019 省 A] 组合数问题
题解
注意到是组合数对一个质数 \(k\) 取余的问题,想到卢卡斯定理,即 \(C_{x}^y = C_{x\mod k}^{y\mod k} \times C_{x/ k}^{y/k} \mod k\)。
发现如果将 \(x,y\) 进行 \(k\) 进制拆分:
\(x = a_0 + a_1 \times k + a_2\times k^2\dots a_i\times k^i,y = b_0 + b_1 \times k + b_2\times k^2\dots b_i\times k^i\)
那么最后结果就是 \(C_{a_0}^{b_0}\times C_{a_1}^{b_1}\dots \times C_{a_i}^{b_i}\mod k\)。
因为 \(a_i,b_i\) 都是属于 \([0,k-1]\) 的,所以对于其中的任意一个组合式,都不可能含有因子 \(k\),因此上式答案等于 \(0\) 的充要条件就是 \(\exist C_{a_i}^{b_i} = 0\),即 \(b_i > a_i\)。存在不好求,正难则反,用总方案数减去 \(\forall a_i\ge b_i\) 的方案数,这样还顺便满足了题目中 \(y\le x\) 的限制。
考虑数位 DP,设 \(f_{i,0/1,0/1}\) 表示,考虑到从低到高的第 \(i\) 位,\(x,y\) 有没有顶到 \(n,m\) 的上界,转移时从高位到低位转移就可以。
DP转移:
- 对于 \(f_{i,1,1}\):当前这一位要都顶到限制,那么上一位也要都顶到限制,又因为要满足 \(a_i\ge b_i\) 的限制,所以 \(f_{i,1,1} = f_{i + 1, 1, 1} \times [a_i\ge b_i]\)
- 对于 \(f_{i,1,0}\):当前这一位 \(x\) 要顶到限制,那么上一位 \(x\) 也要顶到限制,但是对 \(y\) 无要求,所以当 \(y\) 上一位顶到限制时,这一位要满足 \(\le b_i - 1 \and \le a_i\) 的限制(注意,可以选 \(0\),所以要加一);当上一位没有顶到限制时这一位只要 \(\le a_i\) 就可以了 \(f_{i,1,0} = f_{i + 1, 1, 1} \times \min(a_i+1,b_i) + f_{i + 1, 1, 0}\times (a_i + 1)\)
- 对于 \(f_{i,0,1}\):当前这一位 \(y\) 要顶到限制,那么上一位 \(y\) 也要顶到限制,但是对 \(x\) 无要求,所以当 \(x\) 上一位顶到限制时,这一位要满足 \(\le a_i - 1 \and \le b_i\) 的限制;当上一位没有顶到限制时这一位只要 \(\le b_i\) 就可以了 \(f_{i,0,1} = f_{i + 1, 1, 1} \times \max(a_i - b_i,0) + f_{i + 1, 0, 1}\times (k-b_i)\)
- 对于 \(f_{i,0, 0}\):定义函数 \(g(x, y) = \sum _{i=1}^{x - 1}\sum_{j = 1}^{y - 1}[i\ge j]\),易知 若 \(x< y\),则\(g(x,y) = \frac{x\times (x + 1)}{2}\)(只能在 \(x\) 里面选);若 \(x\ge y\),则 \(g(x,y) = \frac{y\times (y + 1)}{2} + (x - y) \times y\)(都在 \(y\) 里选,或一个在多出来的部分,一个在 \(y\) 里)。\(f_{i,0,0} = f_{i + 1,0,0}\times g(k,k)+f_{i + 1,0,1}\times g(k,b_i)+f_{i +1,1,0}\times g(a_i,k)+f_{i + 1,1,1}\times g(a_i,b_i)\)
最后答案就是 \(g_{n + 1,m + 1} - f_{1,0,0} - f_{1,0,1}-f_{1,1,0}-f_{1,1,1}\),时间复杂度 \(O(T\log n)\)。
code
#include<bits/stdc++.h>
#define mod 1000000007
#define int long long
#define inv2 500000004
#define fo(a, b, c) for(int b = a; b <= c; b++)
#define _fo(a, b, c) for(int b = a; b >= c; b--)
using namespace std;
int T, n, m, k, a[70], b[70], f[70][2][2];
int add(int x, int y){return ((x + y) % mod + mod) % mod;}
int g(int x, int y){
if(x < y) return x %= mod, y %= mod, (x + 1) * x % mod * inv2 % mod;// 要先取 min,再取模
else return x %= mod, y %= mod, add((y + 1) * y % mod * inv2 % mod, (x - y) * y % mod);
}
void solve(){
cin >> n >> m;
int L = 0, ans = g(n + 1, m + 1);
while(n || m) ++L, a[L] = n % k, b[L] = m % k, n /= k, m /= k;
memset(f, 0, sizeof f), f[L + 1][1][1] = 1ll;
_fo(L, i, 1){
if(a[i] >= b[i]) f[i][1][1] = f[i + 1][1][1];
f[i][1][0] = add(f[i + 1][1][1] * min(a[i] + 1, b[i]) % mod, f[i + 1][1][0] * (a[i] + 1) % mod);
f[i][0][1] = add(f[i + 1][1][1] * max(a[i] - b[i], 0ll) % mod, f[i + 1][0][1] * (k - b[i]) % mod);
int s1 = add(f[i + 1][0][0] * g(k, k) % mod, f[i + 1][0][1] * g(k, b[i]) % mod);
int s2 = add(f[i + 1][1][0] * g(a[i], k) % mod, f[i + 1][1][1] * g(a[i], b[i]) % mod) ;
f[i][0][0] = add(s1, s2);
}
int s = add(add(f[1][0][0], f[1][0][1]), add(f[1][1][0], f[1][1][1]));
ans = add(ans, -s);
cout << ans << "\n";
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> T >> k;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号