1. 程式人生 > >題解——[ZJOI2010]數字計數 數位DP

題解——[ZJOI2010]數字計數 數位DP

部分 這一 遞推 必須 題意 但是 void div 會有

最近在寫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