1. 程式人生 > >【BZOJ1833】【ZJOI2010】count 數字統計

【BZOJ1833】【ZJOI2010】count 數字統計

1833: [ZJOI2010]count 數字計數

Time Limit: 3 Sec Memory Limit: 64 MB
Description

給定兩個正整數a和b,求在[a,b]中的所有整數中,每個數碼(digit)各出現了多少次。
Input

輸入檔案中僅包含一行兩個整數a、b,含義如上所述。
Output

輸出檔案中包含一行10個整數,分別表示0-9在[a,b]中出現了多少次。
Sample Input

1 99

Sample Output

9 20 20 20 20 20 20 20 20 20

HINT

30%的資料中,a<=b<=10^6;
100%的資料中,a<=b<=10^12。
Source

Day1

顯然,我們可以先計算出[1,a-1],[1,b]中每個數字出現的個數,然後相減即可。問題變為:給一個數n,求1,2,…,n中每個數字出現的次數。
以下敘述中,“i位”都是從低位向高位數。
首先,我們允許前導0,也不考慮數大小的上限,在位數一定時,設為i,每種數字出現的次數是相同的,記為f(i)。不難得出遞推式f(i)=10*f(i-1)+10^(i-1)(將第i位和前i-1位的數字分開考慮即可)。
繼續,如果仍然允許前導0,但將數的上限考慮進來的話,我們只需要從高位向低位計算。設當前為第i位,n的這一位的數字為k,那麼對於那些這一位數字小於k的數,他們的前i-1位是沒有數的大小上限的,每個數字累加k*f(i-1)即可,同時0~k-1的數累加10^(i-1);而對於那些這一位的數字恰好為k的數,為了不超過上限,應累加前i-1位的數字形成的數+1。
最後,如何處理前導0呢?
其實,假設n有m位,我們只需要先統計出1,2,…m-1位數的情況(而這些是沒有上限的,會比較方便),計算時而且保證這些數的首位不為0,然後對於m位同樣的在最高位的時候不將0計算進來即可。
注意行末不要有多餘空格否則PE。

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long LL;
LL f[15],ten[15];
int num[15];
void solve(LL n,LL *cnt){
    if(n==0)return;
    int m=0;
    LL rest=n;
    while(n){
        num[++m]=n%10;
        n/=10;
    }
    for(int i=1;i<m;i++){
        cnt[0]+=f[i-1
]*9; for(int j=1;j<10;j++)cnt[j]+=f[i-1]*9+ten[i-1]; } rest-=num[m]*ten[m-1]; for(int i=1;i<num[m];i++)cnt[i]+=ten[m-1]; for(int i=0;i<10;i++)cnt[i]+=f[m-1]*(num[m]-1); cnt[num[m]]+=rest+1; for(int i=m-1;i;i--){ rest-=num[i]*ten[i-1]; for(int j=0;j<num[i];j++)cnt[j]+=ten[i-1]; for(int j=0;j<10;j++)cnt[j]+=f[i-1]*num[i]; cnt[num[i]]+=rest+1; } } LL cnta[15],cntb[15]; int main(){ ten[0]=1; for(int i=1;i<15;i++){ f[i]=10*f[i-1]+ten[i-1]; ten[i]=ten[i-1]*10; } LL a,b; cin>>a>>b; solve(a-1,cnta); solve(b,cntb); for(int i=0;i<9;i++)printf("%lld ",cntb[i]-cnta[i]); printf("%lld\n",cntb[9]-cnta[9]); return 0; }