题目:
CodeForces 55D
题意:
若一个数能被其各个位上的数整除,即满足条件。求区间[l, r]内所有满足条件的数
方法:
若想要一个数能被其各个数位上的数整除,则此数要能被各个位上的数的LCM整除。而且由于LCM(1~9) = 2520,所以将数对2520取模后,若余数能被LCM整除,则原数也能被LCM整除,因为LCM肯定为2520的一个因子。
(期初几个数位DP都是用循环写的,结果后来发现有dfs的更具普遍应用的写法,更容易理解且能被更广地使用,虽然速度肯定不如循环的写法……但胜在简单实用)
数位DP,通过[l, r] = [1,r]- [1,l-1]求得最终答案(其实此方法也可以看成一种记忆化搜索,只是通过数位DP的思想优化了状态,减少了大量的复杂度)
定义状态:dfs(int n, int lcm, int m, bool flag)表示当前为第n位,之前所有位的LCM为lcm,且对之前构造出来的数对2520取模后为m,flag表示之前枚举的数知否全部到达上限(对于4567,则4512的前两位到达上限),即当前枚举的数是否受限
初始状态:dfs(len,1, 0, true)。
状态转移:
1若n为0,则枚举完了整个数列,返回m% lcm == 0。
2若之前所有数都达到了上限,即flag为true,则此位置可枚举的数为0~num[n](表示原数的第n位)
3若之前的数中有一个没到上限,则此位置无论是什么数,都不可能比原数大(类似循环的数位DP那个思想),则此位置可枚举的数为0~9
优化:由于1~9的数组合成的LCM是离散的,所以通过离散化lcm可减少空间复杂度。通过一个f[n][lcm][m]的数组进行记忆存储,减少时间复杂的,此数组存的是当长度为n,LCM为lcm,余数为m时所有满足条件的数的个数,所以要直接返回f数组的值时,不仅需要f数组的值存在,还要flag为false,即此时不受限
PS:由于第一次f数组开成了f[n][m][lcm]……结果调试了半天……算是自己把自己又坑了一回
测试数据:
2
1 1000
1 10000
答案:
138
752
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<climits>
#include<cstdlib>
using namespace std;
//#define DEBUG
const int MAXMOD = 2520;
typedef __int64 ll;
ll f[20][50][MAXMOD+100];
int h[MAXMOD+100];
int num[20];
ll xGcd(ll a, ll b)
{
return (a % b == 0)? b:xGcd(b, a % b);
}
ll xLcm(ll a, ll b)
{
return a / xGcd(a, b) * b;
}
ll dfs(int n, int lcm, int m, bool flag)
{
#ifdef DEBUG
cout<<"n="<<n<<" flag"<<((flag)? 1:0)<<endl;
#endif
if ( !n ) return m % lcm == 0;
if ( ~f[n][h[lcm]][m] && !flag ) return f[n][h[lcm]][m];
int k = (flag? num[n]:9);
#ifdef DEBUG
cout<<"k="<<k<<endl;
#endif
ll ans = 0;
for (int i = 0; i <= k; i++){
int cLcm = (i)? xLcm(lcm, i):lcm;
int cM = (m * 10 + i) % MAXMOD;
ans += dfs(n - 1, cLcm, cM, flag && (i == k));
}
if ( !flag ) f[n][h[lcm]][m] = ans;
return ans;
}
ll getAns(ll a)
{
#ifdef DEBUG
cout<<"num="<<a<<endl;
#endif
int len = 0;
while ( a ) num[++len] = a % 10, a /= 10;
#ifdef DEBUG
for (int i = len; i; i--) cout<<num[i]<<" ";cout<<endl;
cout<<"dfs="<<dfs(len, 1, 0, true)<<endl;
#endif
return dfs(len, 1, 0, true);
}
int main()
{
//freopen("in.txt", "r", stdin);
int cn = 0;
for(int i = 1; i <= MAXMOD; i++) if ( MAXMOD % i == 0 ) h[i] = cn++;
#ifdef DEBUG
cout<<"cn="<<cn<<endl;
#endif
memset(f, -1, sizeof(f));
int T;
scanf("%d", &T);
while ( T-- )
{
ll l, r;
scanf("%I64d%I64d", &l, &r);
printf("%I64d\n", getAns(r) - getAns(l-1));
}
return 0;
}