PTA A1049Counting Ones(30分)

題目: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;
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章