原题:http://codeforces.com/problemset/problem/55/D
题解:题意给一个区间[l,r],求区间中可以被所有非0数位整除的数的个数。显然可以把区间拆开,[1,l-1]和[1,r]。问题就转化成求1-n的个数。可以发现1-9的最大公约数为2520。考虑如何转化,如果一个数a可以被所有非0数位整除,那他一定可以被2520的一个因整除。这个性质很容易证明任意一个数的数位一定是1-9的子集,一定是2520的因子,不妨记下mod2520的余数和最小公倍数。若可整除就是答案。
考虑数位dp。设f[i][s][k][0/1]表示前i位被2520除余数是S的答案是多少。0/1表示是否到上界。
转移f[i][s][k]=f[i-1][(mode*10+j)%2520][lcm(j,k)]用记忆化搜索解决。
这样的复杂度为O(T * 位数*2520*2520*10*2)这样是过不了的。
因为k只有48个考虑离散化,做个映射就好了
细节:
1.f数组只用清一遍-1
2.从高位到低位枚举
到这里这题就可以过了。。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=2520;
vector<int> dim;
int a[M+10],t,cur;
ll l,r,f[20][M+1][50];
inline int gcd_(int x,int y){return x%y?gcd_(y,x%y):y; }
inline int lcm_(int x,int y){if(!y) return x;else return x/gcd_(x,y)*y;}
ll dfs(int x,int mode,int lcm,int op){
if(!x) return mode%lcm==0?1:0;
if(!op && ~f[x][mode][a[lcm]]) return f[x][mode][a[lcm]];
int maxx=op?dim[x]:9;ll ret=0;
for(int i=0;i<=maxx;i++) ret+=dfs(x-1,(mode*10+i)%M,lcm_(lcm,i),op&(i==maxx));
if(!op) f[x][mode][a[lcm]]=ret;
return ret;
}
ll work(ll x){
dim.clear();dim.push_back(-1);ll t=x;
while(t) dim.push_back(t%10),t/=10;
return dfs(dim.size()-1,0,1,1);
}
int main(){
for(int i=1;i<=M;i++) if(M%i==0) a[i]=++cur;
scanf("%d",&t);
memset(f,-1,sizeof f);
while(t--){
scanf("%lld%lld",&l,&r);
printf("%lld\n",work(r)-work(l-1));
}
return 0;
}
出题人的题解中给了一种优化。若 x | a*10^b,那么x的后b位一定是0且前面的数可以整除a。所以只需要记mode%252和最后一位就行了。
优化版本:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<iostream>
#define ll long long
using namespace std;
const int M=2520;
vector<int> dim;
int a[M+10],t,cur;
ll l,r,f[20][253][50];
inline int gcd_(int x,int y){return x%y?gcd_(y,x%y):y; }
inline int lcm_(int x,int y){if(!y) return x;else return x/gcd_(x,y)*y;}
inline ll rd(){
ll x=0;ll f=1;char s=getchar();
while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
while(isdigit(s)) x=(x<<1)+(x<<3)+s-'0',s=getchar();
return x*f;
}
ll dfs(int x,int mode,int lcm,int op){
if(!x) return mode%lcm==0?1:0;
if(!op && ~f[x][mode][a[lcm]]) return f[x][mode][a[lcm]];
ll maxx=op?dim[x]:9;ll ret=0;
for(ll i = 0; i <= maxx; i++){
// 注意 i=0时 大佬的玄学优化
ret += dfs(x-1, x > 1 ? (mode*10+i)%252 : mode*10+i, lcm_(lcm,i), op&(i==maxx));
}
if(!op) f[x][mode][a[lcm]]=ret;
return ret;
}
ll work(ll x){
dim.clear();dim.push_back(-1);ll t=x;
while(t) dim.push_back(t%10),t/=10;
return dfs(dim.size()-1,0,1,1);
}
int main(){
// freopen("cf55d.in","r",stdin);
for(int i=1;i<=M;i++) if(M%i==0) a[i]=++cur;
t=rd();
memset(f,-1,sizeof f);//只用清一遍-1
while(t--){
l=rd();r=rd();//将区间拆开
printf("%lld\n",work(r)-work(l-1));
}
return 0;
}