數位DP入門 BZOJ 1833 題解(需要複習)
阿新 • • 發佈:2019-02-15
顯然,這篇部落格受PoPoQQQ的影響,程式碼自己敲了一遍,基本上和PoPoQQQ的程式碼一樣,在這裡寫一下題解
1833: [ZJOI2010]count
數字計數
Time Limit: 3 Sec Memory Limit: 64 MB
Submit: 3421 Solved: 1510
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。
註釋程式碼:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define dnt long long
using namespace std;
dnt cnt[10],f[100];
void Resolve(dnt x ,dnt pos){
//在現在這個函式裡,我們可以發現這裡有個while來處理當前位的資訊,請大家模擬12345(認真模擬一下就懂了),之前是處理的now/pos作為x傳過來的,這樣一來可以處理該位是x%10帶來的貢獻
x=10 pos=1000
while(x){
cnt[x%10]+=pos;
x/=10;
}
}
void Digital_DP(dnt x,int ff){
int i,j;
dnt pos,now;
//先定義一個pos表示當前數位的意義
for(i=1,pos=10;pos<x;i++,pos *=10){
for(j=0;j<=9;j++){
cnt[j]+=f[i-1]*9*ff;
//可以這樣考慮,i-1的整體區間的左邊添加了一個數,但是不能為0,所以*9(這是右邊的區間對答案的貢獻)
}
for(j=1;j<=9;j++){
cnt[j]+=pos/10*ff;
//考慮的是最左邊為j的情況,會對答案進行貢獻該位的意義除10,例如j=1時,123456中的1對答案的貢獻是100000,也就是pos/10(此時我們只考慮最高位,零頭一會兒再處理)
}
}
now=pos/=10;i--;//此時的now等於最高位的意義
//注意到pos大於了x才停止迴圈,所以往回走一步
while(now<x){
while(now+pos<=x){
dnt temp=now/pos;
//temp為(當前累加起來的值/當前數位的意義),至於為什麼要這麼搞,下一個函式就清楚了
Resolve(temp,pos*ff);
for(j=0;j<=9;j++){
cnt[j]+=f[i]*ff;
}
now+=pos;//該位的意義一步一步地累加到now裡去
}
pos/=10;i--;
}
}
int main(){
dnt a,b,pos;
int i;
f[1]=1;
for(i=2,pos=10;i<=12;i++,pos*=10){
f[i]=f[i-1]*10+pos;
}
//首先預處理出每一位數字包括前導0的i位數裡有多少它,注意一點,所有的數在某一位包括前導0的情況下個數都是相等的,舉個例子,對於小於十位數來說,所有數出現的機會是均等的,小於兩位數,每個數出現的機會也是均等的,注意這裡處理的是有前導0的,也就是說01,03,09這些都是合法的
cin>>a>>b;
Digital_DP(b+1,1);//注意到這裡是b+1,參看該函式裡用的是統計小於該數的資訊,也就剛好是1到b的資訊
Digital_DP(a,-1);//函式傳的引數1,-1到ff裡,是為了實現一個函式實現區間1到b減去區間1到a-1的操作
for(i=0;i<=9;i++)
printf("%lld%c",cnt[i],i==9?'\n':' ');
//注意到這裡的輸出,我PE了一次
return 0;
}