題解——[ZJOI2010]數字計數 數位DP
最近在寫DP,今天把最近寫的都放上來好了,,,
題意:給定兩個正整數a和b,求在[a,b]中的所有整數中,每個數碼(digit)各出現了多少次。
首先詢問的是一個區間,顯然是要分別求出1 ~ r ,1 ~ l的答案,然後相減得到最終答案
首先我們觀察到,產生答案的區間是連續的,且可以被拆分,
也就是說0 ~ 987的貢獻= 0 ~ 900 + 901 ~ 987的恭喜,
同理,把位拆開也是等價的,所以我們可以單獨計算每個位的貢獻
這樣講可能有點不太清晰,舉個例子吧
3872
我們先把它按數拆開來算:分為0 ~ 999 + 1000 ~ 3872
然後再把1000 ~ 3872的部分按位拆開來算
設f[i][j]為到了第i位j出現的次數(i=2 00 ~ 99),註意允許前導0的存在,只是必須是共i位
對於一個數n,若有t位,那麽前面那部分是t-1位的,後面則是計算t位的(因為前面t-1位沒有限制)
因此我們先加上t-1位的數字出現次數,
首先當i=1時,顯然f[i][j]=1,
當i變為2時,我們可以腦補一下,2位可以拆分為1位+1位,而前面的1位有10種可能,因此後面的1位會出現10次,
而前面的一位也因此使得每種數字要+10,
當i變為3時,我們可以再次腦補,3位可以拆分為1位+2位,前面的一位還是有10種可能,因此後面的2位會出現10次,
而前面的一位也因此使得每種數字要+100
……
於是觀察可得
f[i][j] = f[i-1][j] * 10 + 10^(i-1);
因此第一部分:
ans[j] += f[cnt-1][j];
為什麽只要加這個呢?
因為這個是從0開始的啊!
比如當cnt-1==3時,
f[cnt-1][j]記錄的就是000 ~ 999的出現次數,
顯然包括了1位2位3位的,
但是這樣會有前導0,怎麽辦呢?
把數字列出來稍微觀察一下即可得知:
對於任意一個數而言,其前導0個數為總位數減去有效位數
也就是說0位的數有3個前導0,如000
1位的數有2個前導0,如002
2位的數有1個前導0,如078
3位的數沒有前導0,如364
因此我們只需要分別減去這些位數的前導0即可,
每個位數要減掉的前導0為:對應的前導0個數 * 有多少個數
比如1位的數有2個前導0,那麽這部分的前導0個數為:2 * 9
而0位的數的前導0個數為:3 * 1
這樣可以觀察到在某種情況下後面要乘的那個數是不好計算的,
所以我們通過前綴和來計算它,
第0位時,一共有1個數,
第1位時,一共有10個數(包括0位的),
感覺有點說不清了。。。。
就是說
000 ----> 1個數
001
。
。
009 ---->10個數
010
011
。
。
099 ---->100個數
而我們要做的就是要獲取單獨的每一段(被換行隔開的)有多少個數
那麽很顯然
010 ~ 099的數的個數就是100 - 10,
所以我們在計算的時候記錄第一個sum表示在當前位之前的前綴和,t表示包括當前位的數的總個數,
比如計算到010 ~ 099這一段的時候,sum = 10,t=100,個數為t - sum = 90個
於是現在我們就可以開始計算第2部分了:
1000 ~ 3872
對於這部分,我們按位處理,
方法不太好說,還是用例子說明吧,
從高位開始算,
最高位是3,
那麽很顯然1000~2999這部分是滿的(即都可以取到),因此我們計算的方法就是
用第一位 + 後3位,
對於1000 ~ 1999來說,第一位的1出現了1000次,我們加上,
那後面的部分對應的次數就是000 ~ 999 的次數對不對!也就是f[3][j]了
2000 ~ 2999同理即可,
因此我們要加的f[3][j]的個數就是2
那麽我們現在只需要處理3000~3872了,
同樣按位處理,
第一位的3出現了873次(還有000的一次),我們加上,
然後就是要計算872的貢獻了,
那這部分怎麽求呢?
和計算3872是一樣的啊(是不是很像遞歸),
還是先算000~799
但是也不完全一樣,有一點不同,
因為此時已經不再是第一位了,所以前導0是允許存在的,
所以這個時候,在處理第一位的時候就需要考慮
000 ~ 099了,
而在第一位時只需從100 ~ 199開始考慮,
多加上對應的次數即可。
貌似有點啰嗦了。。。。
下面是代碼
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define LL long long 5 #define AC 15 6 LL l,r,cnt,tot; 7 LL ansl[AC],ansr[AC],f[AC][AC];//f[i][j] : 到了第i位(i = 2,00 ~ 99),j出現的次數 8 int numl[AC],numr[AC]; 9 /*由於枚舉位數,所以不考慮前導零, 10 因此就可以直接遞推次數了? 11 次數必須嚴格保證是i位的,就是說就算是000也要湊夠i位 12 */ 13 void pre() 14 { 15 scanf("%lld%lld\n",&l,&r); 16 l -= 1;//因為我計算的是r - l的,但是l要包括進來,,,,懶得改了,就在這裏減一下吧 17 LL tmp=l; 18 while(tmp) 19 { 20 numl[++cnt]=tmp % 10; 21 tmp /= 10; 22 } 23 tmp=r; 24 while(tmp) 25 { 26 numr[++tot]=tmp % 10; 27 tmp /= 10; 28 } 29 for(R i=0;i<=9;i++) f[1][i]=1; 30 tmp=10; 31 for(R i=2;i<=tot;i++)//枚舉位數 32 { 33 for(R j=0;j<=9;j++) 34 f[i][j] = f[i-1][j] * 10 + tmp;//因為新加入的第一位有10種,所以原來的位數要乘10 35 tmp *= 10; 36 } 37 /*for(R i=1;i<=tot;i++) 38 { 39 for(R j=0;j<=9;j++) 40 printf("%lld ",f[i][j]); 41 printf("\n"); 42 }*/ 43 } 44 45 void work1() 46 { 47 LL tmp=1; 48 for(R i=1;i<cnt;i++) tmp *= 10; 49 for(R j=0;j<=9;j++) ansl[j] += f[cnt-1][j];//就是這裏會產生前導零吧,不過是整段的,可以稍作分析解決 50 LL t=1,sum=0;//現在是t個數碼 51 for(R i=0;i<cnt;i++)//枚舉位數(預先加的只有cnt-1位的ans,因為只有這部分是完整的) 52 { 53 ansl[0] -= (t - sum) * (cnt - 1 - i);//i位被占用,剩下的都是0 54 sum += t - sum;//累加上當前的數碼個數 55 t *= 10;//10 ^ i個數碼 56 } 57 for(R i=cnt; i ;i--) 58 { 59 int b = (i == cnt);//這裏和下面都要特判第一位(因為有0取與不取的問題,即開頭是否可以為0) 60 for(R j=b;j<numl[i];j++)//先統計當前位的 61 ansl[j] += tmp; 62 l -= numl[i] * tmp; 63 ansl[numl[i]] += l + 1; 64 if(i == cnt)//error!!!必須大於等於1才行 65 { 66 for(R j=0;j<=9;j++) 67 ansl[j] += f[i-1][j] * (numl[i] - 1);//這裏只能批量加上前幾個的,後面的是不規則的,要到後面處理 68 } 69 else 70 { 71 for(R j=0;j<=9;j++) 72 ansl[j] += f[i-1][j] * numl[i];//因為之後可以算0的了,所以就不要-1了 73 } 74 tmp /= 10; 75 } 76 } 77 78 void work2() 79 { 80 LL tmp=1; 81 for(R i=1;i<tot;i++) tmp *= 10; 82 for(R j=0;j<=9;j++) ansr[j] += f[tot-1][j];//就是這裏會產生前導零吧,不過是整段的,可以稍作分析解決 83 LL t=1,sum=0;//現在是t個數碼 84 for(R i=0;i<tot;i++)//枚舉位數(預先加的只有tot-1位的ans,因為只有這部分是完整的) 85 { 86 ansr[0] -= (t - sum) * (tot - 1 - i);//i位被占用,剩下的都是0 87 sum += t - sum;//累加上當前的數碼個數 88 t *= 10;//10 ^ i個數碼 89 } 90 for(R i=tot; i ;i--) 91 { 92 int b = (i == tot);//因為只有第一位的0不合法,所以後面是可以統計到0的 93 for(R j=b;j<numr[i];j++)//先統計當前位的 94 ansr[j] += tmp; 95 r -= numr[i] * tmp;//這裏本意就是要獲取低於當前位的數碼,因此前面減掉了的要在r裏面也減掉,不然獲取不出來 96 //ansr[numr[i]] += r - numr[i] * tmp + 1; 97 ansr[numr[i]] += r + 1;//因為前面已經減過了,所以這裏就不要減了 98 if(i == tot)//error!!!必須大於等於1才行 99 { 100 for(R j=0;j<=9;j++) 101 ansr[j] += f[i-1][j] * (numr[i] - 1);//這裏只能批量加上前幾個的,後面的是不規則的,要到後面處理 102 } 103 else 104 { 105 for(R j=0;j<=9;j++) 106 ansr[j] += f[i-1][j] * numr[i];//因為之後可以算0的了,所以就不要-1了 107 } 108 tmp /= 10; 109 } 110 /*for(int i=0;i<=9;i++) printf("%lld ",ansl[i]); 111 cout << endl; 112 for(int i=0;i<=9;i++) printf("%lld ",ansr[i]); 113 cout << endl;*/ 114 for(R i=0;i<9;i++) printf("%lld ",ansr[i] - ansl[i]); 115 printf("%lld\n",ansr[9] - ansl[9]); 116 } 117 118 int main() 119 { 120 // freopen("in.in","r",stdin); 121 pre(); 122 work1(); 123 work2(); 124 // fclose(stdin); 125 return 0; 126 }
題解——[ZJOI2010]數字計數 數位DP