( 1 ≤ l , r ≤ 1 0 18 ) (1\leq l,r\leq 10^{18}) (1≤l,r≤1018)
由题目可得
①:统计数字出现次数;
②:直接暴力计算无法得出;
③:输入给定区间。
满足使用数位DP的条件。
tip:
如果我们暴力求解会发现有许多计算重复,数位DP可以帮助我们运用重复计算的部分。
我们把 a n s l , r ans_{l,r} ansl,r 转化为 a n s 1 , r − a n s 1 , l − 1 ans_{1,r}-ans_{1,l-1} ans1,r−ans1,l−1。
去除一个维度,就成为了一个单调上升的函数。
其核心思想是枚举数位,搜索过程中记忆化。
数位DP学习
统计 5 5 5 和 7 7 7 的个数是朴素的数位DP,统计连续的 7 7 7 个 7 7 7,
我们可以加入一个变量统计连续的 7 7 7 的个数。
详细可以看代码。
#include
#define ll long long
using namespace std;
const int N=50000;
ll dp[20][50][50][20],num[20];
//len表示当前在第几位,limit表示是否贴着上限,lead表示是否有前导零
//sum5统计5的数量,sum7统计7的数量,siz统计连续的个数
ll dfs(int len,bool limit,bool lead,int sum5,int sum7,int siz)
{
ll ans=0;
if(len==0)
return sum5+sum7*3+(siz>=7)*300;
if(!limit && lead && dp[len][sum5][sum7][siz]!=-1) //记忆化
return dp[len][sum5][sum7][siz];
int res=limit==1?num[len]:9; //贴上限
for(int i=0;i<=res;i++)
{
if(i!=5 && i!=7)
ans+=dfs(len-1,limit&&i==res,lead||i,sum5,sum7,siz>=7?7:0);
if(i==5)
ans+=dfs(len-1,limit&&i==res,lead||i,sum5+1,sum7,siz>=7?7:0);
if(i==7)
ans+=dfs(len-1,limit&&i==res,lead||i,sum5,sum7+1,siz>=7?7:siz+1);
//特殊的,如果已经有7个7,不需要考虑了,必定加+300
//不然需要从0开始重新统计
}
if(!limit&&lead) //没有任何限制再加入
dp[len][sum5][sum7][siz]=ans;
return ans;
}
ll work(ll x)
{
int len=0;
while(x) //取数位
{
num[++len]=x%10;
x/=10;
}
return dfs(len,1,0,0,0,0);
}
int main()
{
int T;
ll l,r;
memset(dp,-1,sizeof dp);
cin>>T;
while(T--)
{
scanf("%lld%lld",&l,&r);
ll ans=(work(r)-work(l-1));
printf("%lld\n",ans);
}
}
对于为什么要把 s u m 5 、 s u m 7 、 s i z sum_5 、sum_7、siz sum5、sum7、siz 写进 DP,
当前的状态可能由不同个上一位转移而来,上一位是 5 5 5 ,和上一位是 7 7 7 的结果显然不同,
所以需要对不同的状态进行分开计算。
数位DP模板题,经验++。