ccount 进制拆分(Lucas 数位dp)

ccount

10.20

思路:
对于一个C(n,m) 我们要求的就是它%5后是否为0。
这个nm太大了,我们没有办法直接计算,又发现这个模数P=5是个质数。
考虑Lucas,分解之后就会成为C(a1,b1) * C(a2,b2) * … * C(ai,bi)。
0 <= ai,bi <= 4
要让C(a1,b1) * C(a2,b2) * … * C(ai,bi) = 0 (mod 5),只要让其中任意一个C = 0 (mod 5)就好了。
什么时候能满足条件呢?就是有至少一个(bi > ai)。
也就是说我们对于未拆分的每一个C(n, i)的n和i进行5进制分解。
但是我们发现要处理的是[l,r]一个区间,也就是说我们要处理的是一堆数。
但是这些数是连续的,而且ans满足可减性,所以就是求C(n,1)~C(n,r)的ans - C(n,1)~C(n,l-1)的ans。
那么问题就转换为求有多少个小于x,且每一位为0~4,在至少一位上比n的五进制分解大。
自然就是数位dp啦!!!

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#define LL long long 
using namespace std;

const int N = 110;
const int P = 5;

int up[N], down[N], utot, dtot;
int comb[P][P];
LL _dp[N][5];

void init() {
    for(int i = 0; i < P; i++)
        for(int j = 0; j <= i; j++) {
            if(j == 0 || j == i)
                comb[i][j] = 1;
            else
                comb[i][j] = (comb[i-1][j-1] + comb[i-1][j]) % P;
        }
}

void divi(LL n, int *arr, int &tot) {
    if(n == 0) {
        tot = 1;
        arr[1] = 0;
    } else {
        tot = 0;
        while(n) {
            arr[++tot] = int(n % P);
            n /= P;
        }
    }
}

LL dp(int i, int r, bool flag) {//r是当前状态下%5的答案(其实记录是0或非0就行啦) 
    if(_dp[i][r] != -1 && flag == false) return _dp[i][r];
    if(i == 0 ) return r == 0;
    LL rt = 0;
    int top = flag ? up[i] : P - 1;
    for(int v = 0; v <= top; v++) 
        rt += dp(i - 1, r * comb[down[i]][v] % P, flag && v == top);//模拟公式 
    if(!flag) return _dp[i][r] = rt;
    else return rt;
}

LL solve(LL n) {
    divi(n, up, utot);
    return dp(utot, 1, true);
}

int main() {
    freopen("ccount.in","r",stdin);
    freopen("ccount.out","w",stdout);
    int T; scanf("%d", &T);
    init();
    while(T--) {
        LL l, r, n;
        scanf("%I64d%I64d%I64d", &l, &r, &n);
        divi(n, down, dtot);
        memset(_dp, -1, sizeof(_dp));
        LL ans = solve(r);
        if(l != 0) 
            ans = ans - solve(l - 1);
        printf("%I64d\n", ans);
    }
    return 0;
}

当然dp还可以优化一下,直接预处理出从某一位开始(有最高位限制的情况之下)的方案数,然后处理到一个0的时候就可以直接返回啦。(代码肯定就要丑一点咯)

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#define LL long long
using namespace std;

LL l, r, n, a[100], k, ans, b[100], tot, Sum[100];

LL ipow(LL a, LL b){
    LL rt = 1;
    for( ; b; b>>=1, a=a*a)
        if(b & 1) rt = rt * a;
    return rt;
}

LL dfs(LL step, bool flag){
    if( !step ) return 0LL;
    LL rt = 0;
    if( flag ){
        if(b[step] <= a[step]){
            rt += b[step] * dfs(step-1, 0);
            rt += dfs(step-1, 1);
        }
        else{
            rt += ((b[step] - a[step] - 1) * ipow(5LL, step-1));//此位C=0 
            rt += Sum[step-1];
            rt += (a[step] + 1) * dfs(step-1, 0);
        }
    }
    else{
        rt += (a[step] + 1) * dfs(step-1, 0);
        rt += (4 - a[step]) * (ipow(5LL, step-1));//此位C=0 
    }
    return rt;
}

LL calc(LL x){
    tot = 0;
    for(LL i=0; i<100; i++) b[i] = 0, Sum[i] = 0;
    LL p = x , rt = 0;
    while( p ){
        b[++tot] = p % 5;
        p /= 5;
    }
    Sum[0] = 1;
    for(LL i=1; i<=tot; i++)
        Sum[i] = Sum[i-1] + b[i] * ipow(5LL, i-1);
    rt = dfs(k, 1);
    return rt;
}

int main(){
    freopen("ccount.in", "r", stdin);
    freopen("ccount.out", "w", stdout);
    int T; scanf("%d", &T);
    while ( T-- ){
        scanf("%I64d%I64d%I64d", &l, &r, &n);
        LL p = n ; k = 0 ;
        while( p ){
            a[++k] = p % 5;
            p /= 5;
        }
        printf("%I64d", calc( r ) - calc( l - 1 ));
        printf("\n");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值