洛谷P6669 [清华集训2016] 组合数问题 题解
题意:
给定 $n,m$ 和 $k$,对于 $0≤i≤n,0≤j≤\min(i,m)$
求有多少对 $(i,j)$ 满足 $\dbinom{i}{j}$ 是 $k$ 的倍数。答案对 $10^9+7$ 取模。
输入格式:
第一行有两个整数 $t,k$,其中 $t$ 代表该测试点总共有多少组测试数据。
接下来 $t$ 行每行两个整数 $n,m$。
输出格式:
共 $t$ 行,每行一个整数代表所有的 $0≤i≤n,0≤j≤\min(i,m)$ 中有多少对 $(i,j)$ 满足 $C^j_i$ 是 $k$ 的倍数。
数据范围:
$1≤n,m≤10^{18}$,$1≤t,k≤100$,且 $k$ 是一个质数。
根据 Lucas 定理
这显然是一个与 $k$ 进制有关的拆分。不妨令 $m \gets \min(n,m)$ 。
记 $n$ 在 $k$ 进制下为 $a_la_{l-1}\cdots a_1$ ,并记 $m$ 在 $k$ 进制下为 $b_lb_{l-1}\cdots b_1$ ,那么
可以发现,这个式子中只要有一个 $\dbinom{a_i}{b_i}=0$ , $\dbinom{n}{m}$ 就是 $k$ 的倍数。
由于 $0 \le a_i,b_i < k$ ,所以 $\dbinom{a_i}{b_i}=0$ 当且仅当 $a_i < b_i$ 。
那么问题就转化为,求有多少对 $(i,j)$ 满足 $i$ 在 $k$ 进制下的至少一位比 $j$ 在 $k$ 进制下的那一位小。
这个东西很显然可以容斥,转为求 $i$ 每一位都不小于 $j$ 的方案数,这个可以通过数位dp得到。
即设 $f(i,0/1,0/1)$ 表示考虑到第 $i$ 位,$a_i$ 是否有限制, $b_i$ 是否有限制时的方案数。
转移的话,直接枚举 $a_i,b_i$ ,保证 $a_i \ge b_j$ 即可,这部分是 $\mathcal{O}(k^2)$ 的。那么答案就是
时间复杂度 $\mathcal{O}(T\cdot 4 k^2\log_k n)$
代码:
#include <bits/stdc++.h>
using namespace std;
// #define int long long
// #define INF 0x3f3f3f3f3f3f3f3f
typedef long long ll;
const int mod = 1e9 + 7, inv2 = (mod + 1) / 2;
template<typename T> void up(T &x, T y) { x < y ? x = y : 0; }
template<typename T> void down(T &x, T y) { x > y ? x = y : 0; }
template<typename T> void add(T &x, T y) { (x += y) >= mod ? x -= mod : 0; }
#define rep(i, a, b) for(int i = (a), i##END = (b); i <= i##END; i++)
#define Rep(i, a, b) for(int i = (a), i##END = (b); i >= i##END; i--)
#define N ((int)(66))
int k, a[N], b[N]; ll f[N][2][2];
ll dfs(int u, bool ln, bool lm)
{
if(!u) return 1;
ll &res = f[u][ln][lm]; if(~res) return res; else res = 0;
int upn = ln ? a[u] : k - 1, upm = lm ? b[u] : k - 1;
rep(i, 0, upn) rep(j, 0, min(i, upm))
add(res, dfs(u - 1, ln && i == upn, lm && j == upm));
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
int qwq; cin >> qwq >> k;
while(qwq--)
{
ll n, m; cin >> n >> m; down(m, n);
ll r = (m + 1) % mod * ((m + 2) % mod) % mod * inv2 % mod;
add(r, (n - m) % mod * ((m + 1) % mod) % mod);
memset(f, -1, sizeof(f)); int len1 = 0, len2 = 0;
while(n) { a[++len1] = n % k, n /= k; }
while(m) { b[++len2] = m % k, m /= k; }
rep(i, len2 + 1, len1) b[i] = 0;
cout << (r + mod - dfs(len1, true, true)) % mod << '\n';
}
return 0;
}
参考文献: