题目大意:
求 1(m)到n直接有多少个数字x满足 x可以整出这个数字的每一位上的数字
思路:
整除每一位。只需要整除每一位的lcm即可
但是数字太大,dp状态怎么表示呢
发现 1~9的LCM 是2520 ....也就是说只要对这个数mod2520 剩下的余数能整除lcm就可以整除了。。
计数的时候还有一个技巧,具体见注释
此外这个题还卡常数了,预处理lcm才过了。。
代码如下:
#include <iostream>
#include <stdio.h>
#include<string.h>
#include<algorithm>
#include<string>
#include<ctype.h>
using namespace std;
#define LCM 2520
long long dp[][LCM+][][];
long long n;
int m;
int state[],hs[],s;
bool vi[];
int d[];
int Lcm[][];
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
int lcm(int a,int b)
{
if(b==)
return a;
return a/gcd(a,b)*b;
} void ini()
{
m=;
while(n)
{
d[++m]=n%;
n/=;
}
reverse(d+,d+m+);
}
void DP()
{
memset(dp,,sizeof(dp));
//先放最高位
for(int i=;i<d[];i++)
{
dp[][i][hs[i]][]=; //dp...[0]存储的是前i位的数小于所求数的方案
}
dp[][d[]][hs[d[]]][]=; //dp...[1]存储的是前i位的数等于所求数的方案
//从高位往下转移
for(int i=;i<=m;++i)
{
for(int j=;j<;++j)
dp[i][j][hs[j]][]=; //从大于1的数位开始放1~9都肯定小于所求数
for(int j=;j<;++j)
{
for(int k=;k<s;++k)
{
if(dp[i-][j][k][]==&&dp[i-][j][k][]==)
continue;
for(int t=;t<=;++t)
{
int sum=(j*+t)%LCM;
int L=Lcm[k][t];
dp[i][sum][hs[L]][]+=dp[i-][j][k][]; //在当前位放的数小于等于所求数的当前为数的情况可以继承高位都等于所求数的方案
//否则,只能继承高位小于所求数的方案
if(t<d[i]) //当前位放的数小于所求数的当前位
{
dp[i][sum][hs[L]][]+=dp[i-][j][k][];
}
if(t==d[i]) //当前位放的数等于所求数的当前位
{
dp[i][sum][hs[L]][]+=dp[i-][j][k][];
} }
}
}
}
}
void setstate()
{
memset(vi,,sizeof(vi));
s=;
int tmp;
for(int i=;i<(<<);i++)
{
tmp=;
for(int j=;j<;j++)
{
if((<<j)&i)
{
tmp=lcm(j+,tmp);
}
}
if(vi[tmp]==)
{
state[s]=tmp;
hs[tmp]=s++;
vi[tmp]=;
}
}
for(int i=;i<s;i++)
{
for(int j=;j<=;j++)
Lcm[i][j]=lcm(state[i],j);
}
}
long long cal()
{
long long ans=; for(int i=;i<;i++)
{
for(int j=;j<s;j++)
{
if(i%state[j]==)
ans+=dp[m][i][j][]+dp[m][i][j][];
}
}
return ans;
}
long long solve(long long x)
{
n=x;
ini();
DP();
return cal();
}
int main()
{
long long a,b;
setstate();
int t;
scanf("%d",&t);
while(t--)
{
//scanf("%I64d%I64d",&a,&b);
scanf("%I64d",&a);
//cout<<solve(b)-solve(a-1)<<endl;
cout<<solve(a)<<endl;
}
return ;
}