題目:給定一個區間的兩個區間端點l和r,問在l和r之間有多少滿足下列情況的數字---這個數字應該滿足前一位是後一位的倍數。
分析:這是一個經典的數位DP的題目,我們可以把區間求和轉化成兩個區間的減法,即S[1,r]-S[1,l-1]就是我們求得的答案。
那麼問題就轉化成了求1-n之間滿足情況的數的個數,那麼我們就要使用dp[i][j]求得數字長度爲i,首數字爲j的滿足情況的數的個數,然後累加進行預處理(具體見代碼)。
隨後分爲以下幾種情況討論:1.比數字n位數少的,直接累加sum[i](爲長度爲i的所有滿足情況的數的和,即dp[i][j](j>=1&&j<=9)的和)就可以了。
2.與數字n位數相同,但首數字比n的首數字小的,直接加上dp[n的長度][j](j>=1&&j<n的首字母)
3.與數字n位數相同,且首數字也相同的,使用dfs求解即可。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int Sum[10],dp[10][10];
void init()
{
memset(Sum,0,sizeof(Sum));
memset(dp,0,sizeof(dp));
for(int i=1;i<=9;i++)
{
dp[1][i]=1;
}
for(int i=2;i<=9;i++)
{
for(int j=1;j<=9;j++)
for(int k=1;k<=j;k++)
if(j%k==0)
{
dp[i][j]+=dp[i-1][k];
}
}
for(int i=1;i<=9;i++)
{
for(int j=1;j<=9;j++)
Sum[i]+=dp[i][j];
//printf("%d\n",Sum[i]);
}
}
int dfs(int nm,int st,int len,int dit[])
{
if(nm>dit[st]) return 0;
if(nm<dit[st]) return dp[len-st][nm];
if(nm==dit[st])
{
if(st==len-1) return 1;
int ans=0;
for(int i=nm;i>=1;i--)
if(nm%i==0)
ans+=dfs(i,st+1,len,dit);
return ans;
}
}
int Cal(int n)
{
if(n<=9) return n;
int dit[10];
int len=log10(n)+1;
//cout<<n<<":"<<len<<endl;
int ans=0;
for(int i=1;i<len;i++)
{
ans+=Sum[i];
}
//cout<<"位數少的ans:"<<ans<<endl;
for(int i=len-1;i>=0;i--)
{
dit[i]=n%10;
n/=10;
}
//cout<<"dit0:"<<dit[0]<<endl;
for(int i=1;i<dit[0];i++)
{
ans+=dp[len][i];
}
//cout<<"同位數小於dit0的ans:"<<ans<<endl;
for(int i=dit[0];i>=1;i--)
if(dit[0]%i==0)
ans+=dfs(i,1,len,dit);
//cout<<"Sum-ans:"<<ans<<endl;
return ans;
}
int main()
{
init();
//int dit[10];
int l=0,r=0,num;
/*for(int i=1;i<=20;i++)
{
cout<<i<<":";
num=i;
l=Cal(num);
int len=log10(num);
for(int j=len;j>=0;j--)
{
dit[j]=num%10;
num/=10;
}
int flag=1;
for(int j=0;j<len;j++)
if(dit[j+1]==0||dit[j]%dit[j+1])
{
flag=0;break;
}
r+=flag;
printf("%d %d\n",l,r);
if(l!=r)
{
printf("ERROR!\n");
break;
}
}*/
scanf("%d",&num);
while(num--)
{
scanf("%d%d",&l,&r);
if(l>=1e9) l--;
if(r>=1e9) r--;
l--;
l=Cal(l);
r=Cal(r);
//cout<<l<<" "<<r<<endl;
printf("%d\n",r-l);
}
return 0;
}