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");
    }
}
發佈了308 篇原創文章 · 獲贊 25 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章