1. 程式人生 > 實用技巧 >2^k進位制數

2^k進位制數

題目描述

\(S\) 是長度為 \(w\)\(01\) 串。從串的右邊開始,每 \(k\) 個字元分成一段(最後不夠 \(k\) 個字元的也分成一段),組成一個小於 \(2^k\) 的數。然後這 \(\left\lceil\frac{w}{k}\right\rceil\) 個數將組成一個序列。若這個序列除去前導零後的長度不小於 \(2\) 且序列裡的數嚴格遞增,那麼 \(S\) 就是一個合法的串。求所有合法的串的個數。

解法

計數題。一開始就往組合方面想了。這個直覺似乎是對的。

先除去第一個數不看,即強制選 \(0\)(因為它可能不是 \(2^k\),比較特殊),那麼對於後面的 \(\lfloor \frac{w}{k} \rfloor\)

個數,如果直接取的話,每個數都可以取 \(0\) ~ \(2^p-1\),要滿足嚴格遞增,實際上就是在這 \(2^p\) 個數裡面選 \(\lfloor \frac{w}{k} \rfloor\) 個來組成序列,那麼方案就是

\[\binom{2^p}{\lfloor \frac{w}{k} \rfloor} \]

這樣對嗎?你會發現這是錯的。因為可以有前導零,而且前導零並不是單增的。這就提示我們將前導零拋開,分步計算。現列舉一個 \(i\)強制\(i\) 個數不為 \(0\),有 \(2^k-1\) 種,前面剩下的數全部是 \(0\),有 \(1\) 種,那麼總方案數實際上是

\[\sum_{i=2}^{\lfloor \frac{w}{k} \rfloor} \binom{2^k-1}{i} \]

(序列長度大於等於 \(2\)\(i\) 要從 \(2\) 開始列舉)
現在將第一個數不為 \(0\) 的方案加進來,列舉 \(i\) 表示第一個數選什麼,那麼對於後面 \(\lfloor \frac{w}{k} \rfloor\) 個數,就在大於 \(i\) 的數裡面選。方案就有

\[\sum_{i=1}^{2^{b \bmod k}} \binom{2^k-1-i}{\lfloor \frac{w}{k} \rfloor} \]

答案就是

\[\sum_{i=2}^{\lfloor \frac{w}{k} \rfloor} \binom{2^k-1}{i} + \sum_{i=1}^{2^{b \bmod k}} \binom{2^k-1-i}{\lfloor \frac{w}{k} \rfloor} \]

考慮怎麼求這個東西,我們發現 \(k\) 是一個很小的數,而對於 \(n<m\)\(\binom{n}{m}=0\),所以只需要預處理出 \(n<2^9\) 以內的組合數,用公式直接遞推即可。

Tips

推完式子之後發現要寫高精,長度範圍在 \(200\) 以內。然後寫了高精加之後,直接交了,全部 \(MLE\)。一算空間 \(2^9\times 2^9\times 200\times 4\approx 200 MB\)。然後我就想這道題不能預處理,就開始寫暴力求組合數(以及高精乘和高精除)。寫到一半覺得不可能有這麼麻煩,就去翻題解,發現某大佬用的 \(string\) 來存的高精陣列。得到啟發後,將 \(int\) 換成了 \(char\),空間直接減成 \(50MB\)。一發過了。

Code

(加了一些邊界判定,感性理解即可)

#include<stdio.h>
#include<string.h>
#define N (1<<9)

int n,k;

inline int max(int x,int y){return x>y? x:y;}
struct Big{
    char num[201],len;
    Big(int x=0){memset(num,0,sizeof(num)); len=0; while(x) num[++len]=x%10,x/=10;}
    Big operator +(const Big x){
        Big c; int cur=0;
        c.len=max(x.len,len);
        for(int i=1;i<=c.len;i++){
            c.num[i]+=num[i]+x.num[i]+cur;
            cur=c.num[i]/10;
            c.num[i]%=10;
        }
        if(cur) c.num[++c.len]=cur;
        return c;
    }
    inline void print(){
        for(int i=len;i>=1;i--)
            printf("%d",num[i]);
    }
}C[N][N];

inline int min(int x,int y){return x<y? x:y;}
int main(){
    scanf("%d%d",&k,&n);
    for(int i=0;i<512;i++) C[i][0]=Big(1),C[i][i]=Big(1);
    for(int i=1;i<512;i++)
        for(int j=1;j<i;j++)
            C[i][j]=C[i-1][j]+C[i-1][j-1];
    if(k>=n) printf("0");
    else{
        Big ans=Big(0);
        int p=1<<k; int rg=min(n/k,p-1);
        for(int i=2;i<=rg;i++)
            ans=ans+C[p-1][i];
        if(n%k&&p-1>n/k){
            int p_=1<<(n%k);
            for(int i=1;i<p_&&p-i-1>=n/k;i++)
                ans=ans+C[p-i-1][n/k];
        }
        ans.print();
    }
}
// 3 7