1. 程式人生 > >[bzoj1833][DP]count 數字計數

[bzoj1833][DP]count 數字計數

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。

題解

真的是痛苦。。這題折磨了我三天
一開始想的是二維預處理+區間縮小。結果我發現二維的在縮小的情況下是無法轉移的。。怎麼辦?一分鐘後我發現可以換成三維


設num[i][j][k]為 位數為i的數字,以j開頭中第k種數字有多少個
先想如何預處理吧。首先,每個num[i][j][j]肯定要賦值為10^(i-1)對吧。因為i-1位到i位中,肯定至少有10^(i-1)個數字是以j開頭的。
預處理完後我們列舉i-1位。因為i位數字實際上就是i-1位這些數字+任意一個數字組合而來的。譬如34567這個五位數 實際上就是3+4567這個四位陣列成的嘛。那麼i-1位的結果肯定對於i是有用的。列舉i位數開頭數字j,i-1位數開頭數字k,繼承狀態l
num[i][j][l]+=num[i-1][k][l]
好了我們預處理完了。那麼詢問的區間很明顯是可以用大區間減去小區間對吧。
做一遍0~r再做一遍0~l-1最後輸出就行了
怎麼計算?首先對於位數<邊界數位數 的數字,我們直接加就好了
譬如23005,我們把1~4位數的結果當然全部都要先加起來嘛。然後再處理其他的
對於位數等於邊界數位數的數。我們就要一點一點加了
還是上面這個23005的例子。我們已經預處理過num[5][1][k]
了對吧。而且10000~19999這些數一定可以被加起來。那麼就直接加咯
那20000~23005怎麼辦??觀察一下。可以拆成四位數的方法0~3005對不對?只要加上以2開頭的五位數的個數就好了啊
那麼這樣一層一層疊下去。。就ok了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
LL num[15][15][15];//i位 最高位為j 第k個數字出現次數 
int s[15]; int Get(LL u) { int ret=0; while(u) { s[++ret]=u%10; u/=10; } return ret; } LL pow(int u) { LL ret=1,a=10; while(u!=0) { if(u%2==1)ret=ret*a; a=a*a;u/=2; } return ret; } LL ans[15][2]; void sol(LL u,int op) { if(u<0)return ; int len=Get(u);LL tmp=u; if(u!=0)ans[0][op]++; for(int i=1;i<len;i++) for(int j=1;j<=9;j++) for(int k=0;k<=9;k++)ans[k][op]+=num[i][j][k]; for(int i=1;i<s[len];i++) for(int k=0;k<=9;k++)ans[k][op]+=num[len][i][k]; ans[s[len]][op]+=tmp-(s[len]*pow(len-1))+1; tmp-=(s[len]*pow(len-1)); for(int i=len-1;i>=1;i--) { for(int j=0;j<s[i];j++) for(int k=0;k<=9;k++)ans[k][op]+=num[i][j][k]; ans[s[i]][op]+=tmp-(s[i]*pow(i-1))+1; tmp-=(s[i]*pow(i-1)); } } int main() { LL a,b; scanf("%lld%lld",&a,&b); int lenx=Get(a),leny=Get(b); for(int i=1;i<=12;i++) for(int j=0;j<=9;j++) { num[i][j][j]=pow(i-1); for(int k=0;k<=9;k++) for(int l=0;l<=9;l++) num[i][j][k]+=num[i-1][l][k]; } sol(b,0); sol(a-1,1); for(int i=0;i<9;i++)printf("%lld ",ans[i][0]-ans[i][1]); printf("%lld\n",ans[9][0]-ans[9][1]); return 0; }