Luogu P6669 [清华集训2016] 组合数问题 题解

Link

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;
}
posted @ 2021-10-08 22:50  Acestar  阅读(64)  评论(0编辑  收藏  举报