數位DP入門
數位DP其實是很靈活的,所以一定不要奢求一篇文章就會遍所有數位DP的題,這一篇只能是講清楚一種情況,其他情況遇到再總結,在不斷總結中慢慢體會這個思想,以後說不定就能達到一看到題目就能靈活運用的水平。(其實DP都是這樣……)
這一篇要說的數位DP是一道最簡單的數位DP:http://acm.hdu.edu.cn/showproblem.php?pid=2089
題目大意:多組數據,每次給定區間[n,m],求在n到m中沒有“62“或“4“的數的個數。
如62315包含62,88914包含4,這兩個數都是不合法的。0<n<=m<1000000
試想:我們如果能有一個函數count(int x),可以返回[0,x]之間符合題意的數的個數。那麽是不是直接輸出count(m)-count(n-1)就是答案?
好,那麽下面我們的關註點就在於怎麽做出這個函數。我們需要一個數組。(dp原本就是空間換時間)
我們設一個數組f[i][j],表示i位數,最高位是j的數,符合題意的數有多少個。比如f[1][2]=1; f[1][4]=0; f[2][6]=8 (60,61,63,64,65,66,67,68,69).
我們先不關註這個f有什麽用,我們先關註f本身怎麽求。首先f[1][i]=0(if i==4),f[1][i]=1(if i!=4) (0<=i<=9)。這一步是很顯然的,那麽根據這個題的數據範圍,只需要遞推到f[7][i]就夠用了。那麽稍微理解一下,可以想出遞推式:
f[i][j]=
if (j==4) f[i][j]=0
else if (j!=6) f[i][j]=Σf[i-1][k] (k=0,1,2,3,4,5,6,7,8,9)
else if (j==6) f[i][j]=Σf[i-1][k] (k=0,1,3,4,5,6,7,8,9)
上面的式子也是很顯然的,如果覺得不顯然可以這樣想:i位數,最高位是j的符合條件的數,如果j是4,肯定都不符合條件(因為題目不讓有4),所以直接是0;如果j不是6,那麽它後面隨便取,只要符合題意就可以,所以是f[i-1][k],k可以隨便取的和;如果j是6,後面只要不是2就行,所以是f[i-1][k],k除了2都可以,求和。
這裏要說明一下,認為00052是長度為5,首位為0的符合條件的數,052是長度為3首位為0符合條件的數。
那麽現在我們已經得到了f數組,再重申一下它的含義:i位數,最高位是j的數,符合題意的數有多少個。
現在我們就要關註怎麽利用f數組做出上面我們說的那個函數count(int x),它可以求出[0,x]中符合題意的數有多少個。
那麽我們做這樣一個函數int solve(int x) 它可以返回[0,x)中符合題意的有多少個。那麽solve(x+1)實際上與count(x)是等價的。
那麽現在問題轉化成了:小於x,符合題意的數有多少個?
很簡單,既然小於,從最高位開始比,必定有一位要嚴格小於x(前面的都相等)。所以我們就枚舉哪一位嚴格小於(前面的都相等)。
假設我們現在把x分成了a1,a2,...,aL這樣一個數組,長度為L,aL是最高位。
那麽結果實際上就是這樣:長度為L,最高位取[0,aL-1]的所有的符合題意數的和;再加上長度為L-1,最高位取aL,次高位取[0,aL-1-1]的所有符合題意數的和;再加上……;一直到第一位。
上面有一句話之所以標粗體,是因為這句話並不是對的,但是為了好看,就先這樣寫著。因為我們還需要考慮這種情況:最高位aL如果是4,那麽這句話直接就可以終止了,因為粗體這句話前面的那句話“最高位取aL”是不能成立的。還要考慮這種情況:最高位aL如果是6,那麽這裏並不是能取[0,aL-1-1]的所有(不能取2)。加上這些條件之後就很嚴謹了。
把上面的漢字對應到題目裏,就是我們前面求出來的f[L][0..aL-1] f[L-1][0..aL-1-1],所以稍加思索之後就能寫出程序了。
#include<cstdio>
const int maxn=10;
long long f[maxn][10];
void getdp()
{
f[0][0]=1;
for (int i=1;i<10;i++)
{
for (int j=0;j<10;j++)
{
if (j==4) f[i][j]=0;
else if (j==6)
{
for (int k=0;k<10;k++)
f[i][j]+=f[i-1][k];
f[i][j]-=f[i-1][2];
}
else
{
for (int k=0;k<10;k++)
f[i][j]+=f[i-1][k];
}
}
}
}
int a[maxn];
long long solve(int n)
{
a[0]=0;
while (n)
{
a[++a[0]]=n%10;
n/=10;
}
a[a[0]+1]=0;
long long ans=0;
for (int i=a[0];i>=1;i--)
{
for (int j=0;j<a[i];j++)
if (j!=4 && !(a[i+1]==6 && j==2))
ans+=f[i][j];
if (a[i]==4) break;
if (a[i+1]==6 && a[i]==2) break;
}
return ans;
}
int main()
{
int n,m;
getdp();
while (scanf("%d %d",&n,&m)==2 && (n||m))
{
long long k1=solve(m+1);
long long k2=solve(n);
//printf("::%d,%d::",k1,k2);
printf("%I64d\n",k1-k2);
}
return 0;
}
數位DP入門