数位dp感悟:
有一类与数位有关的区间统计问题。这类问题往往具有比较浓厚的数学味道,无法暴力求解,需要在数位上进行递推等操作
方法与 基本思想:
可以从高到低枚举第一次<n对应位是哪一位
¨需要统计区间[l,r]的满足题意的数的个数,这往往可以转换成求[0,r]-[0,l)
¨对于求区间[0,n)有一个通用的方法。
¨对于一个小于n的数,肯定是从高位到低位出现某一位<n的那一位。
¨预处理f数组。
¨F[i,st] 代表位数为i(可能允许前导0。如00058也是个5位数),状态为st的方案数。这里st根据题目需要确定。
¨如i=4,f[i,st]也就是0000~9999的符合条件的数的个数(十进制)
¨决策第i位是多少(suchas 0~9)
¨F[i,st] = F[i,st] + f[i–1,st']
¨st'为相对应的状态
题解:
求区间[l,r]之间有多少个数,各位数和能被10整除。转化为get(x),区间[0,x]之间有多少个数满足条件。
则答案即为get(r)-get(l-1),问题转化为数位dp。
将x的各位数字储存在bit[]中。
考虑区间[0~5132]
可以转化成[0~999] 、[1000~1999]、[2000~2999]、[3000~3999]、[4000~4999]、[5000~5099]、[5100~5109]、[5110~5119]、[5120~5129]、[5130~5132]
令f[i][j]表示前i位和模10得j的方案数。若i为最后一位,若j=0则f[i][j]=1,否则f[i][j]=0。
用记忆化搜索实现数位dp
dp(int p,int m,bool flag),flag=true 表示当前位p无论取多少都小于数x 。
则dp(i,m) += dp(i+1,(m+k)%10) 0=<k<= (9 or bit[i])
#include <cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#define LL long long
#define bug puts("***********")
#define INF 0x3f3f3f3f
using namespace std;
int bit[100];
LL dp[100][100];
LL DP(int p,int m,int flag)
{
if(p==0) return (m==0);
if(dp[p][m]!=-1&&flag) return dp[p][m];
int k=flag?9:bit[p];
LL sum=0;
for(int i=0;i<=k;i++)
sum+=DP(p-1,(i+m)%10,flag||i!=k); //因为bit[]中的数是倒着存的
if(flag) dp[p][m]=sum;
return sum;
}
LL solve(LL n)
{
memset(dp,-1,sizeof(dp));
int num=0;
if(n<0) return 0; /// 存在n== -1
while(n)
{
bit[++num]=n%10; //bit[]将数倒着存进去了
n/=10;
}
return DP(num,0,0);
}
int main()
{
int t;
LL n,m;
while(~scanf("%d",&t))
{
for(int cas=1; cas<=t; cas++)
{
scanf("%lld%lld",&n,&m);
printf("Case #%d: %lld\n",cas,solve(m)-solve(n-1)); ///注意 n-1
}
}
return 0;
}
方法二:
暴力代码用来找规律
发现: 0-10 1
0-100 10
0-1000 100
0-990 99
0-992 100
0-997 100
基本规律为 n/10 + (1或0)
加1的情况为:n/10*10 到 n 有满足条件的 比如:997: 99 + (990到997是否有满足条件的,如果有则加1)
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
__int64 ff(__int64 m)
{
if(m<0)
return 0;
__int64 temp=m/10,ans;
__int64 i;
ans=temp;
for(i=temp*10;i<=m;i++)
{
__int64 sum=0,t=i;
while(t)
{
sum+=t%10;
t/=10;
}
if(sum%10==0)
ans++;
}
return ans;
}
int main()
{
int tcase ,tt=1;
__int64 a,b;
scanf("%d",&tcase);
while(tcase--)
{
scanf("%I64d%I64d",&a,&b);
printf("Case #%d: %I64d\n",tt++,ff(b)-ff(a-1));
}
return 0;
}