[bzoj1833][DP]count 數字計數
阿新 • • 發佈:2019-01-08
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;
}