1. 程式人生 > 其它 >數位DP——P2602 [ZJOI2010]數字計數

數位DP——P2602 [ZJOI2010]數字計數

題目描述

給定兩個正整數 \(a\)\(b\),求在 \([a,b]\) 中的所有整數中,每個數碼(digit)各出現了多少次。

輸入格式

僅包含一行兩個整數 \(a,b\),含義如上所述。

輸出格式

包含一行十個整數,分別表示 \(0\sim 9\)\([a,b]\) 中出現了多少次。

輸入輸出樣例

輸入
1 99
輸出
9 20 20 20 20 20 20 20 20 20

說明/提示

資料規模與約定
對於 \(30\%\) 的資料,保證 \(a\le b\le10^6\)
對於 \(100\%\) 的資料,保證 \(1\le a\le b\le 10^{12}\)

析(引用oiwiki)

發現對於滿 \(i\)

位的數,所有數字出現的次數都是相同的,故設陣列 \(dp[i]\) 為滿 \(i\) 位的數中每個數字出現的次數,此時暫時不處理前導零。則有 \(dp_i=10\times dp_{i-1}+10^{i-1}\),這兩部分前一個是來自前 \(i-1\) 位數字的貢獻,後一個是來自第 \(i\) 位的數字的貢獻。

有了 \(dp\) 陣列,我們來考慮如何統計答案。將上界按位分開,從高到低列舉,不貼著上界時,後面可以隨便取值。貼著上界時,後面就只能取 0 到上界,分兩部分分別計算貢獻。最後考慮下前導零,第 i 位為前導 0 時,此時 1 到 \(i - 1\) 位也都是 0,也就是多算了將 \(i - 1\)

位填滿的答案,需要額外減去。程式碼中work函式就是計算0到n中每一個數字出現的個數,存在ans陣列中。

做了這道題,我們可以發現,數位dp的時間複雜度和數字大小是沒有太大關係的,數位dp的速度極快,快到幾乎為常數,空間複雜度也不用開很高的陣列。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll l, r;
ll dp[14];
ll rs[14];
ll ans1[14], ans2[14];
ll sum[14];
ll a[14];
void init()
{
	// dp[i] 表示滿i位的數字個數,只需要一維就夠了,因為滿i位的時候,每個數字的個數是一樣的 
	rs[0] = 1;
	for (int i = 1; i <= 13; i++)
	{
		dp[i] = dp[i - 1] * 10 + rs[i - 1];
		rs[i] = 10 * rs[i - 1]; 
	}
//	for (int i = 1; i <= 13; i++)
//	{
//		cout << dp[i] << endl;
//	}
}
void work(ll n, ll * ans)
{
	ll tmp = n;
	int len = 0;
	while (n != 0) // 用a儲存n的每一位 
	{
		len ++;
		a[len] = n % 10;
		n /= 10;
	}
	
	for (int i = len; i >= 1; i--)
	{
		for (int j = 0; j < 10; j++)
			ans[j] += dp[i - 1] * a[i];
		for (int j = 0; j < a[i]; j++)
			ans[j] += rs[i - 1];
		 
		tmp -= rs[i - 1] * a[i];
		ans[a[i]] += tmp + 1;
		ans[0] -= rs[i - 1]; // 前導0需要特殊處理一小下 
	}
}

int main()
{
	ios::sync_with_stdio(false); // 輸入加速(用處不大)
	cin >> l >> r;
	init();
	work(r, ans1);
	work(l - 1, ans2); // 這裡算出0~r的結果和0~l-1的結果,最後相減就是l~r的結果
	for (int i = 0; i < 10; i++)
		cout << ans1[i] - ans2[i] << " ";
	cout << endl; 
	return 0;
}