題目:click me~
題意:給出一個數字n(n<=2^30),求1~n中出現數字1的總數。
解題思路:這道題用枚舉來做肯定是會超時的。恰當的方法是尋找特殊數字找規律,再拓展到一般去。
令n=30710,令數位從低位到高位分別爲1號位、2號位、3號位、4號位及5號位。
(1)考慮1號位在1~n過程中在該位可能出現的1的個數
n在1號位左側是3071。
由於1號位爲0(小於1),因此在1~n中,僅在高四位爲0000~3070的過程中,1號位纔可取到1。
因此在1~n過程中,1號位將出現3071個1(3071*10^0)。
(2)考慮2號位在1~n過程中在該位可能出現的1的個數
n在2號位左側是307,右側是0。
1.由於2號位爲1(等於1),因此在1~n中,僅在高三位爲000~306的過程中,對任意的低一位,2號位才總可以取到1,即0001X、0011X、0021X、…3051X、3061X,總共有3070=307*10^1種情況,這裏10^1是指X可以是0~9任意值。
2.上面高三位只考慮到306,接下來考慮高三位爲307的情況,保持3071,計算30710~n有多少2號位爲1的數。由於n就是30710,因此只有一個數(0+1,0表示2號位右側的數)
因此在1~n過程中,2號位將出現3071個1(307*10^1+0+1)。
(3)考慮3號位在1~n過程中在該位可能出現的1的個數
n在3號位左側是30,右側是10。
由於3號位爲7(大於1),因此在1~n中,在高二位爲00~30過程中,對任意低二位(00~99),3號位均可取到1,即001XX、011XX、021XX、...、281XX、291XX、301XX,總共3100=31*10^2種情況,這裏10^2是指XX可以是00~99的任意值。
因此在1~n過程中,3號位將出現3100個1(31*10^2)。
(4)考慮4號位在1~n過程中在該位可能出現的1的個數
n在4號位左側是3,右側是710.
由於4號位爲0(小於1),因此在1~n過程中,僅在高一位爲0~2的過程中,對任意低三位(000~999),4號位才總可以取到1,即00XXX、01XXX、21XXX,總共有3000=3*10^3種情況,這裏10^3是指XXX可以是000~999任意值。
因此在1~n過程中,4號位將出現3000個1(3*10^3)。
(5)考慮5號位在1~n過程中在該位可能出現的1的個數
n在5號位左側是0,右側是710.
由於5號位爲3(大於1),因此在1~n中,對任意的低四位(即0000~9999),5號位均可取到1,即1XXXX,總共有10000=(0+1)*10^4種情況,這裏10^4是指XXXX可以是0000~9999任意值。
因此在1~n過程中,5號位將出現10000個1(0+1)*10^4.
綜上所述,1~n中共會出現22242=3071+3071+3100+3000+10000個1.
這個看似複雜的例子其實是遵循了下面這個簡潔的計算規則。
步驟一:設需要計算的數爲n,是一個m位的數。從低到高枚舉n的每一位(控制一個a,每次乘10表示進一位),對每一位計算該位爲1的數的個數。
步驟二:設當前處理至第k位,記left爲第k位的高位表示的數,now爲第k位的數,right爲第k位的低位表示的數,根據當前位(now)的情況分三類討論:
1.若now==0,則ans+=left*a;
2.若now==1,則ans+=left*a+right+1;
3.若now>=2,則ans+=(left+1)*a。
code
#include<iostream>
#include<cstdlib>
using namespace std;
int main() {
int n, a = 1, ans = 0;
int left, now, right;
cin >> n;
while (n / a != 0) {
left = n / (a * 10);
now = n / a % 10;
right = n % a;
if (now == 0)ans += left * a;
else if (now == 1)ans += left * a + right + 1;
else ans += (left + 1)*a;
a *= 10;
}
cout << ans;
return 0;
}