Codeforces - 55D Beautiful numbers (数位dp+数论)
题意:求[L,R](1<=L<=R<=9e18)区间中所有能被自己数位上的非零数整除的数的个数
分析:丛数据量可以分析出是用数位dp求解,区间个数可以转化为sum(R)-sum(L-1)前缀和相减的形式。如果一个数能被所有位上数的最小公倍数(lcm)整除,便是符合要求的数。
但是直接传递一个数n的话,dp数组肯定开不下。那么怎么让其传递的值更小呢。
先了解一个等式: sum%(x*n)%x == sum%x
可以将一个数枚举到第pos位之前的值视作sum,x是所有位上数的lcm。那么我们可以在dfs递归的时候传递sum%(x*n),这样就能在维护sum%x性质的同时,使sum变得很小。
x*n怎么取呢?1-9的最小公倍数是2520,显然它作为此处的x*n在空间复杂度上是支持的。
此时可以确定dp数组是三维的。dp[i][j][k],其代表枚举到第i位,前面所有位组成的数%2520的余数是j,前面所有位上的lcm是k的数位状态下符合要求的个数。
还有一个问题,20*2520*2520的数组是开不下的。但是第三维有大部分的数都是不会出现的,所以可以对2520的因数离散化,数量是小于50的,空间复杂度是可以承受的。
#include<bits/stdc++.h> using namespace std; const int maxn =40; typedef long long ll; ll dp[maxn][2520][50]; int a[maxn]; int dic[3000]; ll gcd(ll a,ll b) { if(b==0) return a; return gcd(b,a%b); } void init() { int MOD=2520,cnt=0; for(int i=1;i<=MOD;++i){ if(MOD%i==0) dic[i]=cnt++; } memset(dp,-1,sizeof(dp)); } ll dfs(int pos,int mod=0,int lcm = 1,bool limit=true) { if (pos==-1) return mod%lcm==0; int id =dic[lcm]; if(!limit && dp[pos][mod][id]!=-1) return dp[pos][mod][id]; int up = limit?a[pos]:9; ll res=0; for(int i=0;i<=up;++i){ if(i) res+=dfs(pos-1,(mod*10+i)%2520,lcm*i/gcd(lcm,i),limit && i==a[pos]); else res+=dfs(pos-1,(mod*10)%2520,lcm,limit && i==a[pos]); } if(!limit) dp[pos][mod][id] = res; return res; } ll solve(ll n) { int pos=0; while(n){ a[pos++]=n%10; n/=10; } return dfs(pos-1); } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif init(); int T; scanf("%d",&T); while(T--){ ll L,R; scanf("%lld%lld",&L,&R); printf("%lld\n",solve(R)-solve(L-1)); } return 0; }
为了更好的明天