数位动态规划
求给定区间内满足某种数位条件(数位之和,数码个数等)的数量,核心思想为“逐位确定”,复杂度log2n
「一本通 5.3 例 1」Amount of Degrees
求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K 个互不相等的B的整数次幂之和。
17 = 24+ 20,
18 = 24+ 21,
20 = 24+ 22.
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int f[35][35];//int型存储
int x, y, b;
ll pos[35];
int len;
void init()//预处理,结果为杨辉三角
{
memset(f,0,sizeof(f));
f[0][0]=1;
for(int i=1; i<=33; i++)
{
f[i][0]=1;
for(int j=1; j<=i; j++)
{
f[i][j]=f[i-1][j-1]+f[i-1][j];//状态转移方程
}
}
}
ll solve(int t, int k)
{
len=0;
while(t)//按进制存储
{
pos[++len]=t%b;
t/=b;
}
ll ans=0;
for(int i=len;i>=1;i--)
{
if(pos[i]>1)//不允许系数出现,在此计算后转出
{
ans+=f[i-1][k]+f[i-1][k-1];
break;
}
else if(pos[i]==1)//左子树加入,进入右子树
{
ans+=f[i-1][k];
k--;
}
if(k<0)
break;
}
return ans;
}
int main()
{
init();
int k;
while(~scanf("%d%d%d%d", &x, &y, &k, &b))
{
ll ans1 = solve(y+1, k);
ll ans2 = solve(x, k);
printf("%lld\n", ans1-ans2);//1到y减去1到x
}
return 0;
}
「一本通 5.3 例 2」数字游戏
题目描述
科协里最近很流行数字游戏。某人命名了一种不降数,这种数字必须满足从左到右各位数字成小于等于的关系,如 123,446。现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。
输入格式
有多组测试数据。每组只含两个数字 a,b,意义如题目描述。(a,b<231-1)
输出格式
每行给出一个测试数据的答案,即 [a,b]之间有多少不降数。
样例输入
1 9
1 19
样例输出
9
18
#include <bits/stdc++.h>
#define ll long long
using namespace std;
int f[11][11];
int d[11];
int a,b;
int dp(int v,int u,int flag)
//v代表当前位,u代表当前状态,flag判断是否越界
//从高位向低位搜,v递减
{
if(v==0) return 1;//枚举完了
if(!flag && f[v][u]!=-1)//优化步,如果不越界且这步已经搜完,直接返回搜的结果
{
return f[v][u];
}
int ans=0;
int n;//结束标志
if(flag)
n=d[v];//如果当前枚举的数越界,也就是擦边,那么枚举到最大
else
n=9;//如果当前不擦边,就随便枚举
for(int i=u; i<=n; i++)
{
if(flag&&i==n)
ans+=dp(v-1,i,1);
else
ans+=dp(v-1,i,0);
}
if(!flag) f[v][u]=ans;//如果未越界,存入该位搜到u的结果
return ans;
}
ll solve(int num)
{
memset(f,-1,sizeof(f));
int cnt=0;
while(num)
{
d[++cnt]=num%10;//倒序存入数据各位
num/=10;
}
return dp(cnt,0,1);//cnt为位数
}
int main()
{
while(~scanf("%d%d",&a,&b))
{
printf("%lld\n",solve(b)-solve(a-1));
}
return 0;
}
洛谷P2657 [SCOI2009]windy数
洛谷题解,详细明了
windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,在A和B之间,包括A和B,总共有多少个windy数?
输入格式
包含两个整数,A B。
输出格式
一个整数
输入输出样例
输入 #1
1 10
输出 #1
9
输入 #2
25 50
输出 #2
20
说明/提示
100%的数据,满足 1 <= A <= B <= 2000000000 。
——————————————————————————————————————————
本题预处理为dp[i][j]=Σdp[i-1][k],然后进行3部分操作
1.求i-1位所有windy数
2.求i位但首位小于所求数首位的windy数
3.首位为所求数首位,逐位求比所求数该位小的windy数再相加,即
∑
i
=
1
位
数
−
1
\sum_{i=1}^{位数-1}
∑i=1位数−1dp[i][j]
#include<bits/stdc++.h>
using namespace std;
//设dp[i][j]为长度为i中最高位是j的windy数的个数
int p,q,dp[11][11],a[11];
void init()//预处理
{
for(int i=0;i<=9;i++)
dp[1][i]=1; //0,1,2,3,4...9都属于windy数
for(int i=2;i<=10;i++)
{
for(int j=0;j<=9;j++)
{
for(int k=0;k<=9;k++)
{
if(abs(j-k)>=2)
dp[i][j]+=dp[i-1][k];
}
}
}//从第二位开始 每次枚举最高位j 并找到k 使得j-k>=2
}
int solve(int x)
{
memset(a,0,sizeof(a));
int len=0,ans=0;
while(x)
{
a[++len]=x%10;
x/=10;
}
//分为几个板块 先求len-1位的windy数 必定包含在区间里的
for(int i=1;i<=len-1;i++)
{
for(int j=1;j<=9;j++)
{
ans+=dp[i][j];
}
}
//然后是len位 但最高位<a[len]的windy数 也包含在区间里
for(int i=1;i<a[len];i++)
{
ans+=dp[len][i];
}
//接着是len位 最高位与原数相同的 最难搞的一部分
for(int i=len-1;i>=1;i--)
{
//i从最高位后开始枚举
for(int j=0;j<=a[i]-1;j++)//j要在a[i]之下
{
if(abs(j-a[i+1])>=2) ans+=dp[i][j]; //判断和上一位(i+1)相差2以上
//如果是 ans就累加
}
if(abs(a[i+1]-a[i])<2) break;
// if(i==1) ans+=1;
}
return ans;
}
int main()
{
init();
scanf("%d%d",&x,&y);
printf("%d\n",solve(y+1)-solve(x));
return 0;
}
}
CF1036C Classy Numbers(类似数字游戏)
定义一个数字是“好数”,当且仅当它的十进制表示下有不超过3个数字1∼9
举个例子:4,200000,10203是“好数”,然而4231,102306,7277420000不是
给定[l,r],问有多少个x使得l≤x≤r,且x是“好数”
一共有T(1≤T≤104 )组数据,对于每次的询问,输出一行一个整数表示答案
1≤li≤ri≤1018
输入
4
1 1000
1024 1024
65536 65536
999999 1000001
输出
1000
1
0
2
#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll f[20][5];
ll d[20];
ll a,b;
int poi;
ll dp(int v,int u,bool flag)
//v代表当前位,u代表非0的个数,flag判断是否越界
//从高位向低位搜,v递减
{
if(v==0) return 1;//枚举完了
if(!flag && f[v][u]!=-1)//优化步,如果不越界且这步已经搜完,直接返回搜的结果
{
return f[v][u];
}
ll ans=0;
int n;//结束标志
if(flag)
n=d[v];//如果当前枚举的数越界,也就是擦边,那么枚举到最大
else
n=9;//如果当前不擦边,就随便枚举
for(int i=0; i<=n; i++)
{
if(i==0)
{
if(flag&&i==n)
ans+=dp(v-1,u,1);
else
ans+=dp(v-1,u,0);
}
else if(u<3)//i!=0&&u>=3即跳出
{
if(flag&&i==n)
ans+=dp(v-1,u+1,1);
else
ans+=dp(v-1,u+1,0);
}
}
if(!flag) f[v][u]=ans;//如果未越界,存入该位搜到u的结果
return ans;
}
ll solve(ll num)
{
memset(f,-1,sizeof(f));
int cnt=0;
while(num)
{
d[++cnt]=num%10;//倒序存入数据各位
num/=10;
}
return dp(cnt,0,1);//cnt为位数
}
int main()
{
scanf("%d",&poi);
while(poi--)
{
scanf("%lld%lld",&a,&b);
printf("%lld\n",solve(b)-solve(a-1));
}
return 0;
}
CF1073E Segment Sum
题目描述
You are given two integers l and r ( l≤r ). Your task is to calculate the sum of numbers from l to r (including l and r ) such that each number contains at most k different digits, and print this sum modulo 998244353 .
For example, if k = 1 then you have to calculate all numbers from l to r such that each number is formed using only one digit. For l=10,r=50 the answer is 11 + 22 + 33 + 44 = 110.
输入格式
The only line of the input contains three integers l , r and k ( 1≤l≤r<1018,1≤k≤10 ) — the borders of the segment and the maximum number of different digits.
输出格式
Print one integer — the sum of numbers from l to r such that each number contains at most k different digits, modulo 998244353 .
题意翻译
给定K,L,R,求L~R之间最多不包含超过K个数码的数的和。
K<=10,L,R<=1e18
输入输出样例
输入 #1
10 50 2
输出 #1
1230
输入 #2
1 2345 10
输出 #2
2750685
输入 #3
101 154 2
输出 #3
2189
说明/提示
For the third example the answer is 101 + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 + 121 + 122 + 131 + 133 + 141 + 144 + 151 = 2189
此题需要用到状压dp,此为90分,之后来补100分
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int d[20];
ll a,b;
int k;
ll xx;
void dp(int v,int u,int u0,int u1,int u2,int u3,int u4,int u5,int u6,int u7,int u8,int u9,bool flag,ll ans)
{
ans%=mod;
if(v==0)
{
xx=(xx+ans)%mod;
//printf("%lld\n",ans);
return ;
}
int n;
if(flag)
n=d[v];
else
n=9;
for(int i=0; i<=n; i++)
{
if((i==0&&u0==1)||(i==1&&u1==1)||(i==2&&u2==1)||(i==3&&u3==1)||(i==4&&u4==1)||(i==5&&u5==1)||(i==6&&u6==1)||(i==7&&u7==1)||(i==8&&u8==1)||(i==9&&u9==1))
{
if(flag&&i==n)
dp(v-1,u,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
else
dp(v-1,u,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
}
else if(ans==0&&i==0)
{
if(flag&&i==n)
dp(v-1,u,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
else
dp(v-1,u,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
}
else if(u<k)
{
if(flag&&i==n)
{
if(i==0) dp(v-1,u+1,1,u1,u2,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
else if(i==1) dp(v-1,u+1,u0,1,u2,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
else if(i==2) dp(v-1,u+1,u0,u1,1,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
else if(i==3) dp(v-1,u+1,u0,u1,u2,1,u4,u5,u6,u7,u8,u9,1,ans*10+i);
else if(i==4) dp(v-1,u+1,u0,u1,u2,u3,1,u5,u6,u7,u8,u9,1,ans*10+i);
else if(i==5) dp(v-1,u+1,u0,u1,u2,u3,u4,1,u6,u7,u8,u9,1,ans*10+i);
else if(i==6) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,1,u7,u8,u9,1,ans*10+i);
else if(i==7) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,1,u8,u9,1,ans*10+i);
else if(i==8) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,u7,1,u9,1,ans*10+i);
else if(i==9) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,u7,u8,1,1,ans*10+i);;
}
else
{
if(i==0) dp(v-1,u+1,1,u1,u2,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
else if(i==1) dp(v-1,u+1,u0,1,u2,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
else if(i==2) dp(v-1,u+1,u0,u1,1,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
else if(i==3) dp(v-1,u+1,u0,u1,u2,1,u4,u5,u6,u7,u8,u9,0,ans*10+i);
else if(i==4) dp(v-1,u+1,u0,u1,u2,u3,1,u5,u6,u7,u8,u9,0,ans*10+i);
else if(i==5) dp(v-1,u+1,u0,u1,u2,u3,u4,1,u6,u7,u8,u9,0,ans*10+i);
else if(i==6) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,1,u7,u8,u9,0,ans*10+i);
else if(i==7) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,1,u8,u9,0,ans*10+i);
else if(i==8) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,u7,1,u9,0,ans*10+i);
else if(i==9) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,u7,u8,1,0,ans*10+i);
}
}
}
}
ll solve(ll num)
{
xx=0;
int cnt=0;
while(num)
{
d[++cnt]=num%10;//倒序存入数据各位
num/=10;
}
dp(cnt,0,0,0,0,0,0,0,0,0,0,0,1,0);
return xx;//cnt为位数
}
int main()
{
scanf("%lld%lld%lld",&a,&b,&k);
if(k==0)
{
printf("0\n");
return 0;
}
else if(k==10)
{
ll ans1=(a+b)*(b-a+1)/2;
printf("%lld\n",ans1);
return 0;
}
ll ans1=solve(b)-solve(a-1);
ans1=(ans1%mod+mod)%mod;
printf("%lld\n",ans1);
return 0;
}
P4317 花神的数论题(类似Amount of Degrees)
题目描述
设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,要问你
∏
i
=
1
n
\prod_{i=1}^{n}
∏i=1n sum(i) ,也就是sum(1)∼sum(N) 的乘积。
输入格式
一个正整数 N。
输出格式
一个数,答案模 10000007 的值。
输入输出样例
输入
3
输出
2
说明/提示
对于 100% 的数据,N≤10^15
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod= 10000007;
ll f[150][150];//int型存储
ll n;
ll pos[70];
int len;
ll quickmod(ll a,ll b)//快速幂
{
ll r=1;
while(b)
{
if(b&1)
r=r*a%mod;
a=a*a%mod;
b/=2;
}
return r%mod;
}
void init()//预处理,结果为杨辉三角
{
memset(f,0,sizeof(f));
f[0][0]=1;
for(int i=1; i<=60; i++)//开多了会爆long long 具体开到多少我也不知道
{
f[i][0]=1;
for(int j=1; j<=i; j++)
{
f[i][j]=f[i-1][j-1]+f[i-1][j];//状态转移方程
}
}
}
int weishu(ll t)
{
len=0;
while(t)//按进制存储
{
pos[++len]=t%2;
t=t/2;
}
return len;
}
ll solve(int k)
{
ll ans=0;
for(int i=len;i>=1;i--)
{
if(pos[i]==1)//左子树加入,进入右子树
{
ans=ans+f[i-1][k];
k--;
}
if(k<0)
break;
}
return ans;
}
int main()
{
init();
int k;
while(~scanf("%lld",&n))
{
weishu(n+1);
ll ans=1;
for(int k=1;k<=len;k++)
{
ll ans1 = solve(k);
ans=(ans*quickmod(k,ans1))%mod;//因为ans1要做幂数,此前操作不可取模
}
printf("%lld\n",ans);
}
return 0;
}
P4999 烦人的数学作业
给出一个区间L~R,求L到R区间内每个数的数字和,如123这个数的数字和为1+2+3=6。
同学们纷纷做出来了,Mr.G一看这最后一题跟摆设没区别了呀,于是他迅速修改了题目,把范围定得非常非常大,且有T组数据,将最终的答案mod 109+7。
(1≤L≤R≤1018 ) (1≤T≤20)
同学们纷纷被难住了。但H为了备战NOIP2018,没有时间完成Mr.G的数学作业~~(其实是不想做QwQ)~~,所以Ta找到了你,希望你帮助Ta和同学完成这烦人的数学作业!
输入格式
输入共T+1行,
第1行读入T。代表有T组数据;
第2~T+1行。读L i和R i
输出格式
输出共T行,
每行输出L i和R i的区间数字和mod 109+7
输入 #1
2
24 69
70 120
输出 #1
411
498
说明/提示
对于 50% 的数据,1≤L≤R≤108;
对于 100% 的数据,1≤L≤R≤1018
1≤T≤20。
此题与数字计数可用同种方法解决,此处给出数位dp做法
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod= 1000000007;
ll l,r,f[20][20],ans[10];
int d[20],len,t;
ll dp(int k,int v,int u,bool flag)
{
if(v==0)
return u;
if(!flag&&f[v][u]!=-1)
return f[v][u];
int n=flag ? d[v]:9;
ll sum=0;
for(int i=0; i<=n; ++i)
{
sum+=dp(k,v-1,u+(i==k),flag&&(i==n));
}
if(!flag)
f[v][u]=sum;
return sum;
}
void change(ll num)
{
len=0;
while(num)
{
d[++len]=num%10;
num/=10;
}
}
void solvel(ll num)
{
change(num);
for(int i=1; i<=9; i++)
{
memset(f,-1,sizeof(f));
ans[i]=(ans[i]-dp(i,len,0,1)+mod)%mod;
}
}
void solver(ll num)
{
change(num);
for(int i=1; i<=9; i++)
{
memset(f,-1,sizeof(f));
ans[i]=dp(i,len,0,1)%mod;
}
}
int main()
{
scanf("%lld",&t);
while(t--)
{
ll ansl=0;
scanf("%lld%lld",&l,&r);
solver(r);
solvel(l-1);//此两处都计算了0,相减即相消
for(int i=1; i<=9; i++)
ansl=(ansl+i*(ans[i]+mod)%mod+mod)%mod;
printf("%lld\n",ansl);
}
return 0;
}
P4124 [CQOI2016]手机号码
题目描述
人们选择手机号码时都希望号码好记、吉利。比如号码中含有几位相邻的相同数字、不含谐音不吉利的数字等。手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售。为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数量。
工具需要检测的号码特征有两个:号码中要出现至少 3 个相邻的相同数字;号码中不能同时出现 8 和 4。号码必须同时包含两个特征才满足条件。满足条件的号码例如:13000988721、23333333333、14444101000。而不满足条件的号码例如:1015400080、10010012022。
手机号码一定是 11 位数,前不含前导的 0。工具接收两个数 L 和 R,自动统计出 [L,R] 区间内所有满足条件的号码数量。L 和 R 也是 11 位的手机号码。
输入格式
输入文件内容只有一行,为空格分隔的 22个正整数 L,R。
输出格式
输出文件内容只有一行,为 1 个整数,表示满足条件的手机号数量。
输入输出样例
输入 #1
12121284000 12121285550
输出 #1
5
说明/提示
样例解释:满足条件的号码: 12121285000、 12121285111、 12121285222、 12121285333、 12121285550。
数据范围:1010 ≤L≤R<1011 。
看到这篇题解才终于有种了解数位dp的感觉,还是太菜了,枯了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod= 1000000007;
ll l,r,f[20][20][20][2][2][2][2];
int d[20],len,t;
ll dp(int v,int a,int b,bool c,bool flag,bool p4,bool p8)
{
if(p4&&p8)
{
return 0;
}
if(v<=0) return c;
if(f[v][a][b][c][flag][p4][p8]!=-1)
return f[v][a][b][c][flag][p4][p8];
int n=flag ? d[v]:9;
ll sum=0;
for(int i=0; i<=n; i++)
{
sum+=dp(v-1,i,a,c||(i==b&&i==a),flag&&(i==n),p4||(i==4),p8||(i==8));
}
f[v][a][b][c][flag][p4][p8]=sum;
return sum;
}
void change(ll num)
{
len=0;
while(num)
{
d[++len]=num%10;
num/=10;
}
}
ll solve(ll num)
{
//if(num<1e10) return 0;
memset(f,-1,sizeof(f));
change(num);
ll ans=0;
for(int i=1;i<=d[11];i++)
{
ans+=dp(10,i,0,0,i==d[11],i==4,i==8);
}
return ans;
}
int main()
{
scanf("%lld%lld",&l,&r);
printf("%lld\n",solve(r)-solve(l-1));
return 0;
}
P4127 [AHOI2009]同类分布
题目描述
给出两个数a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
输入格式
一行,两个整数a和b
输出格式
一个整数,表示答案
输入输出样例
输入 #1
10 19
输出 #1
3
说明/提示
对于所有的数据,1 ≤ a ≤ b ≤ 1018
万万没想到还有枚举sum和做模数的操作,学到了学到了
ps.卡常技巧:首先,我们可以剪枝,若sum(数字各位之和>mod),直接return 0,因为我们越往后,sum只会越来越大,不可能等于mod。
然后就是清空DP数组时将memset改为for循环,我们只用将我们上一次用到的数组清空(1~枚举的mod)就可以了(实测跑的飞快)
再然后就是搜索,正常情况下我们是这样写的:
cout<<calc(b)-calc(a-1);
但是我们忽视了一个问题,那就是相同的mod我们应该只记搜一次,但因为我们分开来计算,所以就算了两次,常数巨大>_<
那么我们就在计算b的答案的同时顺便减去a的答案就行了,这样我们每个mod只用到了一遍。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll l,r,f[20][2][200][200];
int d[20],len,t;
int mod;
int suml;
ll dp(int v,bool flag,int sum,int num)
{
if(v==0)
{
if(num==0&&sum==mod)
return 1;
else
return 0;
}
if(f[v][flag][sum][num]!=-1)
return f[v][flag][sum][num];
int n=flag ? d[v]:9;
ll ans=0;
//printf("1# %d %d %d\n",v,d[v],n);
for(int i=0; i<=n; i++)
{
if(sum+i!=0)
{
//printf("2# %d %lld %lld\n",v,sum+i,num*10+i);
ans+=dp(v-1,flag&&(i==n),sum+i,(num*10+i)%mod);
}
else
{
//printf("3# %d %lld %lld\n",v,sum+i,num*10+i);
ans+=dp(v-1,flag&&(i==n),sum+i,(num*10+i)%mod);
}
}
f[v][flag][sum][num]=ans;
return ans;
}
void change(ll num)
{
len=0;
suml=0;
while(num)
{
d[++len]=num%10;
suml+=num%10;
num/=10;
}
}
ll solve(ll num)
{
change(num);
ll ans=0;
for(mod=1;mod<=9*len;mod++)
{
memset(f,-1,sizeof(f));
ans+=dp(len,1,0,0);
}
return ans;
}
int main()
{
while(~scanf("%lld%lld",&l,&r))
{
printf("%lld\n",solve(r)-solve(l-1));
}
return 0;
}
CF55D Beautiful numbers
题目描述
Volodya是一个很皮的男♂孩。他认为一个能被它自己的每一位数上的数整除的数是很妙的。我们先忽略他的想法的正确性(如需证明请百度“神奇海螺”),只回答在l到r之间有多少个很妙的数字。
输入:
总共有t个询问:
第一行:t;
接下来t行:每行两个数l和r。
注意:请勿使用%lld读写长整型(虽然我也不知道为什么),请优先使用cin(或者是%I64d)。
输出:
t行,每行为一个询问的答案。
输入 #1
1
1 9
输出 #1
9
输入 #2
1
12 15
输出 #2
2
提示
1<=t<=10,
1<=li<=ri<=9⋅1018
此题做法还是常规做法,就是时间卡的我脑壳痛。。
需要注意的是,每次搜不需要将dp数组清空,每次都是相同的值,注意这个问题后跑的飞快
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=2520;
ll l,r,f[20][2550][50];
int a[2550];
int d[20],len;
int gcd(int x,int y)
{
return y ? gcd(y,x%y):x;
}
int lcm(int x,int y)
{
if(!y)return x;
return x/gcd(x,y)*y;
}
ll dp(int v,int num,int lc,bool flag)
{
if(v==0) return num%lc==0;
if(!flag&&f[v][num][a[lc]]!=-1)
return f[v][num][a[lc]];
int n=flag ? d[v]:9;
ll ans=0;
for(int i=0; i<=n; i++)
{
ans+=dp(v-1,(num*10+i)%mod,lcm(lc,i),flag&&(i==n));
}
if(!flag)
f[v][num][a[lc]]=ans;
return ans;
}
void change(ll num)
{
len=0;
while(num)
{
d[++len]=num%10;
num/=10;
}
}
ll solve(ll num)
{
change(num);
return dp(len,0,1,1);
}
int main()
{
ios::sync_with_stdio(false);
int cnt=0;
for(int i=1;i<=mod;i++)
{
if(mod%i==0)
a[i]=++cnt;
}
memset(f,-1,sizeof(f));
int poi;
cin>>poi;
while(poi--)
{
cin>>l>>r;
cout<<(solve(r)-solve(l-1))<<endl;
}
return 0;
}