问题描述
解析
这道题是要求 i 和 j 在一定范围内取值, 能够取出多少对 (i, j) 满足C(i, j) % k = 0 ,由于用例的数字很大,无法用排列组合的公式直接计算来求余,因此需要使用到数论的一个定理——Lucas定理。
定理的详细证明可以去百度百科看,这里只针对这个定理的使用,举个栗子:
通过使用这个定理,C(n, m)通过不断地将 n 和 m 进行 /k 和 %k 操作从而分解为若干个C(a, b)相乘求余,其实这个操作和求k进制数是一样的,横着看可以发现142为47的5进制数,344为99的5进制数。
k进制数每一位是不超过k的,由于k为质数,一堆小于k的数相乘不可能为k的倍数,因此要使得余数为0只能其中有C(a, b) = 0,而要使得C(a, b) = 0 等价于 a < b,也就是C(i, j)中 i 的 k 进制表示至少有一位上小于 j 的对应位。
比如:C(69, 47) % 5, 69的5进制为234, 47的5进制为142,第二位上 3 < 4,因此C(69, 47) % 5 = 0。
因此本题变为在 n 和 m 为上界的条件下,求有多少组(i, j) (i >= j)满足k进制表示中,i至少有一位是小于j的。
以C(p, q)为例,考虑每一位的取值情况,定义三个函数:
long long cal(long long x, long long y); //a取值[0,x] 和 b取值[0,y] 情况下 a >= b 的种类数
long long cal_1(long long x, long long y); //a取定值x 和 b取值[0,y] 情况下 a >= b 的种类数
long long cal_2(long long x, long long y); //a取值[0,x] 和 b取定值y 情况下 a >= b 的种类数
而每一位的取值和前面的位是否取到 n 和 m 的上界有关,若前面已取到上界,则该位只能取不大于n和m对应位的值。假设5进制数为143123,若前三位已取143,则第四位只能取小于等于1的数,形成递推关系,考虑如下4个状态。
dp[i][0] p和q前 i 位都未达到n和m的上界
dp[i][1] p前 i 位达到n上界,q未达到m上界
dp[i][2] p前 i 位未达到n上界,q达到m上界
dp[i][3] 前 i 位p和q分别达到n和m上界
此处分析两种递推关系,其余同理可推:
1.dp[i][0]可以由dp[i - 1][0]转移,由于前面未到上界,因此第i位p和q的取值范围为[0, k - 1]。
也可以由dp[i - 1][1]转移,由于前i-1位p已达n上界,因此p的第i位必须取小于n对应位的数,而前面q未达到上界,因此q的第i位可以取值[0, k - 1]。
也可以由dp[i-1][2]转移,p的第i位可取[0, k - 1],q的第i位必须要取小于m对应位的数。
与可以由dp[i-1][3]转移,由于前面都达到上界,因此p和q都必须取小于对应位的数。
2.dp[i][3]只能由dp[i-1][3]转移,因为p是要大于q的,所以还要与上n的当前位大于m的当前位。
因此求出n ≥ m的总方案数,减去k进制情况下p每一位都大于等于q每一位的方案数,即可得到p至少有一位小于q对应位的方案数,即C(p, q) % k = 0的方案数。
完整代码
#include<bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int inv_2 = 5e8 + 4;
long long cal(long long x, long long y); //a取值[0,x] 和 b取值[0,y] 情况下 a >= b 的种类数
long long cal_1(long long x, long long y); //a取定值x 和 b取值[0,y] 情况下 a >= b 的种类数
long long cal_2(long long x, long long y); //a取值[0,x] 和 b取定值y 情况下 a >= b 的种类数
int main()
{
int t, k;
cin >> t >> k;
long long n, m;
int a[100], b[100]; //分别记录n和m的k进制数
for (int turn = 0; turn < t; ++turn) {
cin >> n >> m;
if (m > n) m = n; // 0 <= j <= min(i, m) 1 <= i <= n
long long ans = cal(n, m); //总方案数(i的取值大于等于j)
int len_a = 0, len_b = 0;
while (n) {
a[len_a++] = n % k;
n /= k;
}
while (m) {
b[len_b++] = m % k;
m /= k;
}
int len = max(len_a, len_b);
vector<vector<long long>> dp(len + 1, vector<long long>(4));
dp[len][3] = 1;
for (int i = len - 1; i >= 0; --i) { //高位到低位
dp[i][0] = dp[i + 1][0] * cal(k - 1, k - 1) + dp[i + 1][1] * cal(a[i] - 1, k - 1)
+ dp[i + 1][2] * cal(k - 1, b[i] - 1) + dp[i + 1][3] * cal(a[i] - 1, b[i] - 1);
dp[i][1] = dp[i + 1][1] * cal_1(a[i], k - 1) + dp[i + 1][3] * cal_1(a[i], b[i] - 1);
dp[i][2] = dp[i + 1][2] * cal_2(k - 1, b[i]) + dp[i + 1][3] * cal_2(a[i] - 1, b[i]);
dp[i][3] = dp[i + 1][3] & (a[i] >= b[i]);
dp[i][0] %= Mod, dp[i][1] %= Mod;
dp[i][2] %= Mod, dp[i][3] %= Mod;
}
ans -= dp[0][0] + dp[0][1] + dp[0][2] + dp[0][3];
ans %= Mod;
if (ans < 0) ans += Mod;
cout << ans << endl;
}
return 0;
}
long long cal(long long x, long long y) {
if (x < y) {
x %= Mod;
return (x + 2) * (x + 1) % Mod * inv_2 % Mod;
}
x %= Mod, y %= Mod;
return ((y + 2) * (y + 1) % Mod * inv_2 % Mod + (x - y) * (y + 1) % Mod) % Mod;
}
long long cal_1(long long x, long long y) {
return min(x, y) + 1;
}
long long cal_2(long long x, long long y) {
if (x < y) {
return 0;
}
return x - y + 1;
}
代码部分解释
const int Mod = 1e9 + 7;
const int inv_2 = 5e8 + 4;
long long cal(long long x, long long y) {
if (x < y) {
x %= Mod;
return (x + 2) * (x + 1) % Mod * inv_2 % Mod;
}
x %= Mod, y %= Mod;
return ((y + 2) * (y + 1) % Mod * inv_2 % Mod + (x - y) * (y + 1) % Mod) % Mod;
}
cal函数:p取值[0, x],q取值[0, y],如果x小于y,则当p取0,q取0,p取1,q取1、0,p取x,y取[0,x], 因此总可能数为 1 + 2 + 3 + ……+ (x + 1),即(x + 2) * (x + 1) / 2,但这个数可能太大了,题上已给出要对 Mod = 1e9 + 7求余,由于(a * b ) % p = ((a % p) * (b % p)) % p,所以c / 2 % Mod = (c / 2 * (Mod + 1)) % Mod = c % Mod * (Mod + 1) / 2 % Mod,inv_2 = (Mod + 1) / 2 = 5e8 + 9。
参考链接
https://blog.csdn.net/xuxiaobo1234/article/details/108096786