蓝桥杯真题——组合数问题(Lucas定理与动态规划)

问题描述

在这里插入图片描述
在这里插入图片描述

解析

这道题是要求 i 和 j 在一定范围内取值, 能够取出多少对 (i, j) 满足C(i, j) % k = 0 ,由于用例的数字很大,无法用排列组合的公式直接计算来求余,因此需要使用到数论的一个定理——Lucas定理
定理的详细证明可以去百度百科看,这里只针对这个定理的使用,举个栗子:
在这里插入图片描述

通过使用这个定理,C(n, m)通过不断地将 nm 进行 /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

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值