题意:给定区间[l, r],求区间内能被自己各个位上非零数字整除的数的个数。
解:被自己各个位上非0数字整除,也就是为各个位上数字最小公倍数的倍数。设dp[pos][lcm]为到第pos位,最小公倍数为lcm的数的个数。显然转移过程中还要记录目前数的大小,最后判断能不能除得尽lcm。dp[pos][lcm][sum]要开到dp[20][2520][9e18],需要优化一下。介于1-9的lcm=2520,可以把sum缩到2520内;lcm的个数是有限的,为49个,所以整体可以缩到dp[20][50][2520],很合适。然后就可以往板子里套了。
其实不是很会设数位dp的状态,基本就是把递归的参数往里塞,最后优化一下。
代码:
#include <bits/stdc++.h> using namespace std; #define maxx 100005 #define maxn 25 #define maxm 205 #define ll long long #define inf 1000000009 #define mod 2520 char a[1005]; int len; ll dp[20][50][2525]={0}; int to[2525]={0}; ll gcd(int a,int b){ return b==0?a:gcd(b,a%b); } ll lcmm(int a,int b){ return a*b/ gcd(a,b); } ll dfs(ll pos,ll lcm,ll sum,ll limit){ if(pos==0) { return sum%lcm==0; } if(!limit&&dp[pos][to[lcm]][sum]!=-1) return dp[pos][to[lcm]][sum]; ll ret=0; ll res=limit?a[pos]:9; for(int i=0;i<=res;i++){ int lcm2=i?lcmm(lcm,i):lcm; ret+=dfs(pos-1,lcm2,(sum*10+i)%mod,limit&&(i==a[pos])); } return !limit?dp[pos][to[lcm]][sum]=ret:ret; } ll solve(ll x){ len=0; while (x){ a[++len]=x%10; x/=10; } return dfs(len,to[1],0,1); } signed main() { int T; //scanf("%d",&T); cin>>T; memset(dp,-1,sizeof dp); int cnt=0; for(int i=1;i<=2520;i++){ if(2520%i==0){ to[i]=++cnt; } } while(T--) { ll l,r; cin>>l>>r; cout<<solve(r)- solve(l-1)<<'\n'; } return 0; } //dp[pos][lcm][mod]