Luogu P6669 [清华集训2016] 组合数问题 题解
Description
给定 \(n,m,k\),对于所有的 \(0\le i \le n,0\le j \le min(i,m)\) 有多少对 \((i,j)\) 满足 \(C_i^j \mod k = 0\)
\(1\le n,m \le 10^{18},\ 1 \le k \le 100\) 且 \(k\) 为质数。
Soluton
数位dp+Lucas定理
因为 \(k\) 是质数,所以 \(C_n^m \mod k \equiv C_{n\mod k}^{m \mod k} \times C_{n/k}^{m/k} \mod k\)
又因为当 \(n < m\) 时,\(C_n^m=0\),就合法了。
因此我们只需要在数位dp时,记一下是否存在一位 \(n_i < m_i\)。
这里数位dp时需要同时处理两个数,第二个数的边界也需要特殊处理。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
const int p = 1e9 + 7;
ll n, m, k;
int a[100], b[100], len;
ll f[100][2][2][2][2];
void init()
{
scanf("%lld%lld", &n, &m);
ll mx = max(n, m);
len = 0;
while(mx) len++, mx /= k;
memset(a, 0, sizeof(a));
for(int i = 1; i <= len; i++)
a[i] = n % k, n /= k;
memset(b, 0, sizeof(b));
for(int i = 1; i <= len; i++)
b[i] = m % k, m /= k;
memset(f, -1, sizeof(f));
}
ll dfs(int cur, bool lima, bool limb, bool cmp, bool flag)
{
if(~f[cur][lima][limb][cmp][flag]) return f[cur][lima][limb][cmp][flag];
if(!cur) return flag;
int upa = lima ? a[cur] : k - 1, upb = limb ? b[cur] : k - 1;
ll res = 0;
for(int i = 0; i <= upa; i++)
for(int j = 0; j <= (cmp ? upb : min(i, upb)); j++)
res = (res + dfs(cur - 1, lima & (i == upa), limb & (j == upb), cmp | (i > j), flag | (i < j))) % p;
return f[cur][lima][limb][cmp][flag] = res;
}
int main()
{
int T;
scanf("%d%lld", &T, &k);
while(T--)
{
init();
printf("%lld\n", dfs(len, 1, 1, 0, 0));
}
return 0;
}
$$A\ drop\ of\ tear\ blurs\ memories\ of\ the\ past.$$