1. 程式人生 > >51nod 1009:數字1的數量

51nod 1009:數字1的數量

 收藏  關注 給定一個十進位制正整數N,寫下從1開始,到N的所有正數,計算出其中出現所有1的個數。 例如:n = 12,包含了5個1。1,10,12共包含3個1,11包含2個1,總共5個1。 Input
輸入N(1 <= N <= 10^9)
Output
輸出包含1的個數
Input示例
12
Output示例
5

沒怎麼學過數位dp,打算好好搞一下~

dp[x]代表1~10^x-1 中1出現的次數,當然0~9出現的次數都是這個。

然後從後往前掃,假設該位的值是digit,那麼如果digit大於1就加上digit*dp[len-1]。len代表當前掃描的長度。

然後還有就是以1開頭的pow(10,len-1),後面的數字任意,會發現這個時候算重複了反而是正確的,因為比方說11計算了兩次,但也因為11有兩個1,所以從不同的角度計算出來的11就不用考慮重複的情況了。

如果digit等於1,有一些麻煩,那麼就等於原來的數量result(把它想象成在以1開頭的數裡面,這裡面1的數量就是原來的result) + dp[len-1]這個數量,剩下的就是有多少個以1開頭的數字,這個頭還沒有算,而這個數量就是除了1開頭剩下的tail的數量。

好比141,到第三個1的時候,實際上已經算出了1~41的1的數量,那我把這個數量想象成是在100~141裡面除開開頭的1的數量,加上42個開頭的1,這個還沒算。然後還有的就是1~99的1的數量。

程式碼:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <string>
#include <cstring>
#pragma warning(disable:4996)
using namespace std;

typedef long long ll;

ll s;
ll dp[20];

void init()
{
	memset(dp, 0, sizeof(dp));
	
	int i;
	for (i = 1; i <= 19; i++)
	{
		dp[i] = dp[i - 1] * 10 + pow(10, i - 1);
	}
}

ll count(ll x)
{
	ll result = 0;
	ll len = 0;
	ll digit = 0;
	ll radix = 1;
	ll tail = 0;
	while (x != 0)
	{
		digit = x % 10;
		x = x / 10;
		
		++len;
		
		if (digit > 1)
		{
			result += radix + digit*dp[len-1];//radix就代表10的多少多少次方,這個時候重複算反而是對的
		}
		else if (digit == 1)
		{
			result += tail + 1 + dp[len-1];//+1是代表取的那個整數
		}
		tail = tail + digit*radix;
		radix *= 10;
	}
	return result;
}

int main()
{
	//freopen("i.txt","r",stdin);
	//freopen("o.txt","w",stdout);
	
	init();
	
	cin >> s;
	cout << count(s)<<endl;
	
	//system("pause");
	return 0;
}