传送门
解析:
看到这道题很容易就想到状压加数位DP,这是常规做法,但是会T,我们先来看一下这样做的代码。
//O(不能过)
ll l,r;
int mark[20];
ll f[20][512][252][2];
int len;
inline
ll dp(cs int &pos,cs int &cotain,int mod,cs bool &limit){
if(pos>len){
int cnt=0;
for(int re i=1;i<=9;++i){
if((cotain&(1<<(i-1)))&&mod!=mod/i*i)return 0;
}
return 1;
}
mod-=mod/252*252;
if(~f[pos][cotain][mod][limit])return f[pos][cotain][mod][limit];
ll ret=0;
int last;
if(limit)last=mark[pos];
else last=9;
for(int re i=0;i<=last;++i){
ret+=dp(pos+1,i?cotain|(1<<(i-1)):cotain,((mod<<1)+(mod<<3))+i,limit&&(last==i));
}
return f[pos][cotain][mod][limit]=ret;
}
inline
ll solve(ll a){
memset(f,-1,sizeof f);
for(len=1;a;++len){
mark[len]=a-a/10*10;
a/=10;
}
--len;
reverse(mark+1,mark+len+1);
return dp(1,0,0,true);
}
int main(){
while(~scanf("%lld%lld",&l,&r)){
outint(solve(r)-solve(l-1));
pc('\n');
}
return 0;
}
我们按照数位DP的常规做法,将其拆分,再记忆化搜索一下,记录除以
2520
2520
2520(
l
c
m
(
1
−
9
)
lcm(1-9)
lcm(1−9))的余数(
m
o
d
mod
mod),由于除8的余数和除5的余数可以用最后一位数搞定,我们可以最后这一维记录252的余数。
以及这个数字包含哪些数(
c
o
t
a
i
n
cotain
cotain),这个用状态压缩实现。
然后跑一遍简单的数位DP。
然后你就被卡住了。
我们来分析一下复杂度:
最坏情况下是一个以9构成的字符串。我们每一次需要搜索的状态数就是
17
∗
512
∗
252
∗
2
=
4386816
17*512*252*2=4386816
17∗512∗252∗2=4386816,这个状态我们每次要搜两个数,还有
100
100
100组数据。这个计算量已经超过
8
e
9
8e9
8e9了,肯定会被卡。(上下界剪枝的不要有意见,这道题一样有数据卡上下界剪枝,我也写过)
事实上,我们把时限调到7000ms都没过。
那我们再想一想,这里还有什么性质没有被我们利用。
要求被几个数整除,那就是要求被它们的最大公约数同时整除。。。
那么我们为什么要记录有哪些数字出现过?我们直接记录出现的非 0 0 0数的最大公约数就行了啊。
然而,这个方法还能继续优化,因为最大公约数最大只有2520,但是这样空间就显得有点吃力。
但是,可能出现的最大公约数有这么多吗?
我们打个表。用下方代码中的 g e t l c m getlcm getlcm函数,可以在 O ( 2 10 ) O(2^{10}) O(210)时间内处理出所有 1 − 9 1-9 1−9的lcm。输出cnt,只有48种。
我们再考虑一个优化,去掉limit。
我们现在的 f [ n o w ] [ l ] [ r e m a i n ] f[now][l][remain] f[now][l][remain]表示的就是当前位为now,出现数字的 l c m lcm lcm为 l l l,当前余数为 r e m a i n remain remain,这里的 r e m a i n remain remain就必须是2520的余数了,因为我们不记录上一位。而我们的 f f f只记录 l i m i t limit limit为假的时候的答案,什么意思?就是指我们在小于等于当前位置的所有位可以随便填,不用担心会超出我们搜索的数大小。
而这样一处理,我们发现不需要每次初始化 f f f数组了。
显然,复杂度就这样降下来了。
l
i
m
i
t
limit
limit为假的复杂度是均摊的。
l
i
m
i
t
limit
limit为真的复杂度是数位个数,就是
O
(
l
o
g
10
N
)
O(log_{10}N)
O(log10N)。
这样就不用担心超时了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
inline
ll getint(){
re ll num;
re int c;
while(!isdigit(c=gc()));num=c^48;
while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
return num;
}
inline
void outint(ll a){
static char ch[23];
if(a==0)pc('0');
while(a)ch[++ch[0]]=a-a/10*10,a/=10;
while(ch[0])pc(ch[ch[0]--]^48);
}
inline
int gcd(int b,int a){return b?gcd(a-a/b*b,b):a;}
inline
int lcm(int a,int b){return a*b/gcd(a,b);}
int pos[2523];
int cnt;
inline
void getlcm(int now,int l){
if(!pos[l])pos[l]=++cnt;
if(now>=10)return;
getlcm(now+1,lcm(l,now));
getlcm(now+1,l);
}
ll f[20][50][2524];
int mark[20],len;
inline
ll dp(int now,int l,int remain,bool limit){
if(!now)return remain%l==0;
if(!limit&&~f[now][pos[l]][remain])return f[now][pos[l]][remain];
ll s=0;
int r;
if(!limit)r=9;
else r=mark[now];
for(int re i=0;i<=r;++i){
int nxt;
if(i)nxt=lcm(i,l);
else nxt=l;
s+=dp(now-1,nxt,(remain*10+i)%2520,limit&&i==r);
}
if(!limit)f[now][pos[l]][remain]=s;
return s;
}
ll solve(ll a){
len=0;
do{
mark[++len]=a-a/10*10;
a/=10;
}while(a);
return dp(len,1,0,1);
}
ll l,r;
signed main(){
getlcm(2,1);
memset(f,-1,sizeof f);
while(~scanf("%lld%lld",&l,&r))outint(solve(r)-solve(l-1));pc('\n');
return 0;
}