題目:參考數位DP,統計1-N中含有“49”的總數 另一篇參考文章HDU 3555 Bomb(1-n含有“49”的數字個數)
補充于2017-1-17
我一直在考慮如何思考這種類型的問題,后來從離散數學中找到了一個思路,就是對于計算的計數,如果有可能我們希望能找到一組遞推公式,然后按照遞推公式進行計算,動態規劃其實就是這個思路,可能我理解還是不夠深刻!
針對這題我們可以定義一組遞推公式。
S0: 代表所有的可能的組合數
S1: 代表所有的不含有49的組合數
S2: 代表所有的含有49的組合數
一般情況:
S0(n) = 10 ^ n;
S1(n) = S0(n) - S2(n);
S2(n) = S2(n-1) * 10 + S1(n-2);
對于整數n的最高位的情況有:
當最高的兩位的整數值大于等于49的時候有:
S2(n) = S2(n-1) * 最高位的整數值 + S1(n-2)
否則有:
S2(n) = S2(n-1) * 最高位的整數值
上述的計數方法比下面講解的思路方法要好點,就是我們找到一個公共的方法用來解決類似的問題。這里先給個代碼:
#include <vector>
#include <iostream>
using namespace std;
long long Calculate(unsigned int n)
{
vector<int> vecNum;
for (int i = n; i > 0; i /= 10) {
vecNum.push_back(i % 10);
}
int nl = vecNum.size();
if (nl <= 1) return 0;
vector<vector<long long> > vecMatrix(nl+1, vector<long long>(2, 0));
vecMatrix[0][0] = 1;
vecMatrix[0][1] = 0;
vecMatrix[1][0] = 10;
vecMatrix[1][1] = 0;
// normal
for (int i = 2; i < nl; ++i) {
vecMatrix[i][1] = vecMatrix[i - 1][1] * 10 + vecMatrix[i - 2][0];
vecMatrix[i][0] = (long long)(pow(10, i)) - vecMatrix[i][1];
}
if (vecNum[nl-1] < 4 || (vecNum[nl-1] == 4 && vecNum[nl-2] < 9)) {
return vecMatrix[nl-1][1] * (vecNum[nl-1]+1);
} else {
return vecMatrix[nl-1][1] * (vecNum[nl-1]+1) + vecMatrix[nl-2][0];
}
}
int main(int argc, char ** argv)
{
int n = 9999;
cout << Calculate(n) << endl;
return 0;
}
思路:上述的鏈接其實已經給了一個算法的思路,但是個人覺得這個算法的思路并不是很好,第一,這個算法思路是個野路子,你得能想到這個DP方程,你才有可能繼續進行下去,第二,這個里面涉及到了狀態轉化,代碼中的狀態轉化也不是很容易理解。
所以,個人總結了另一套不同的算法這個算法采用了類似容斥原理的方式。
我們使用一般性的例子,比如n只能形如9, 99, 999, 9999 這樣的形式。這種形式降低了解題的復雜度,針對這種變形的題目,我們可以有如下思路。
- 形如X49Y,其中X代表高位的序列,Y代表低位的序列,我們可以先求得Y序列中不包含49的全部序列的數的總和A(Y),然后計算序列X的序列的數的總和B(X),使用A(Y)乘上B(X)就是此時49所處的位置的所有可能取值的數的總和。
- 從低位開始計算每一位49的總和,然后全部相加就是所求的1-N中含有49的總數。
- 針對A(Y) 我們可以建立一張DP狀態方程解的表。提高運行的效率。
針對上述的算法,我們僅僅需要考慮如何計算高位的B(X)的可能次數,我們使用GetHighCount函數的方法達到我們的要求。這樣我們的算法就能適應輸入N的要求。
另外還有個細節,需要特殊考慮最高位。
代碼如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
long long GetHighCountForLow(int nBegin, int nEnd)
{
return (long long)pow(10, nEnd - nBegin);
}
long long GetLowCount(const vector<int> & vecN, int nBegin, int nEnd, vector<long long> & dp);
long long CalCountInclude49ForLow(const vector<int> & vecN, int nBegin, int nIndex, int nEnd, vector<long long> & dp)
{
long long X = GetHighCountForLow(nBegin, nIndex);
long long Y = GetLowCount(vecN, nIndex+2, nEnd, dp);
return X * Y;
}
long long GetHighCount(const vector<int> & vecN, int nBegin, int nEnd)
{
long long sum = 0L;
for (int i=nBegin; i<nEnd; ++i) {
sum *= 10L;
sum += vecN[i];
}
if (vecN[nEnd] >= 4)
sum += 1L;
return sum;
}
long long GetLowCount(const vector<int> & vecN, int nBegin, int nEnd, vector<long long> & dp)
{
if (nBegin == nEnd) return 1;
if (nBegin > nEnd) return 0;
if (dp[nBegin] > -1)
return (long long)pow(10, nEnd-nBegin) - dp[nBegin];
long long sum = 0L;
for (int i=nBegin; i<nEnd; ++i) {
sum += CalCountInclude49ForLow(vecN, nBegin, i, nEnd, dp);
}
dp[nBegin] = sum;
return (long long)pow(10, nEnd-nBegin) - dp[nBegin];
}
long long CalCountInclude49(const vector<int> & vecN, int nBegin, int nIndex, int nEnd, vector<long long> & dp)
{
long long X = GetHighCount(vecN, nBegin, nIndex);
long long Y = GetLowCount(vecN, nIndex+2, nEnd, dp);
return X * Y;
}
long long CalCountInclude49(long long n)
{
vector<int> vecN;
for (long long i=n; i>0; i/=10) {
vecN.push_back(i % 10);
}
reverse(vecN.begin(), vecN.end());
if (vecN.size() <= 1) return 0;
if (vecN.size() <= 2) {
if (vecN[0] > 5
|| (vecN[0]==4 && vecN[1] ==9))
return 1;
else
return 0;
}
vector<long long> dp(vecN.size(), -1);
long long sum = 0L;
for (int i=1; i<vecN.size(); ++i)
sum += CalCountInclude49(vecN, 0, i, vecN.size(), dp);
if (vecN[0] > 5
|| (vecN[0]==4 && vecN[1] ==9)) {
sum += CalCountInclude49(vecN, 0, 0, vecN.size(), dp);
}
return sum;
}
int main(int argc, char ** argv)
{
long long n;
cin >> n;
cout << CalCountInclude49(n) << endl;
return 0;
}
我這篇文章的之所以我認為是簡單的,因為我的思路相比較參考的文章,我的維度比他的低,我就用了一個一維的數組進行DP。更重要的是,如果你多次測試你會發現DP數組是一個固定的序列,這個序列就是數位(整數的位數)數含有49的個數。我們可以事先生成這張表,而不用每次進行DP計算求得。