CF55D Beautiful numbers
题目大意
一个正整数是“美丽的”,当且仅当它能被其所有非零数字整除。统计给定区间内美丽数的个数。\((1≤l_i≤r_i≤9\cdot 1^18)\)
分析
显然数位 \(DP\),那么我们来考虑一下需要记录什么。
- 因为约束条件是关于数位与数字整体的,所以需要记录当前数字 \(pre\) 大小。
- 想要被所有数位整除,一个显然的转化是利用 \(lcm\) 表示整除关系。
- 常规的上界标志 \(lim\)。
现在有了需要记录的信息,进一步分析时空复杂度:
先来看空间复杂度:
- 首先 \(pre\) 的数值太大,我们当然可使用
unordered_map,但在这里考虑另外一种方法,利用一个神奇的转化技巧:\[A \mod a_i=A \mod lcm(a_1,a_2\cdots a_i\cdots a_n) \mod a_i \]而一到九的 \(lcm\) 很容易求出是 \(2520\),从而可以通过给 \(pre\) 取模降低空间复杂度。 - 其次,\(1\) 到 \(9\) 任意数字组合的不过 \(36\) 种,因此 \(lcm\) 可以通过离散化进一步降低空间复杂度。
再来看时间复杂度:
- 首先求 \(lcm\) 时预处理 \(gcd\),常规操作。
- 然后,数位 \(DP\) 本身就是对多种状态的剪枝,因此对于多测的数位 \(DP\) 题而言,不必每次清空 \(dp\) 数组,之后直接复用即可,这样可以极大地加快运行速度。但是,值得注意的是如果想要复用之前的 \(dp\) 状态,就不能记忆 \(lim\) (上界)这类对于询问有特殊性地信息!
code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20,p=2520;
int t;
int dp[N][2600][2][50],GCD[50][10];
int num[N],sta[3000],id[3000];
int dfs(int pos,int pre,int lim,int lcm){if(!pos) return !(pre%lcm);if(~dp[pos][pre%p][lim][sta[lcm]]&&!lim)return dp[pos][pre%p][lim][sta[lcm]];int maxn(lim?num[pos]:9);int res(0);for(int i=0;i<=maxn;++i){int ppre=(pre*10+i);int llcm=!i?lcm:lcm/GCD[sta[lcm]][i]*i;res+=dfs(pos-1,ppre,lim&&i==maxn,llcm);}if(!lim) dp[pos][pre%p][lim][sta[lcm]]=res;return res;
}
int sol(int n){num[0]=0;for(int x=n;x;x/=10) num[++num[0]]=x%10;return dfs(num[0],0,1,1);
}
signed main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);for(int i=1;i<=p;++i)if(!(p%i)) id[sta[i]=++sta[0]]=i;for(int i=1;i<=sta[0];++i)for(int j=1;j<=9;++j) GCD[i][j]=__gcd(id[i],j);memset(dp,-1,sizeof(dp));cin>>t;for(int l,r;t--;){cin>>l>>r;cout<<sol(r)-sol(l-1)<<endl;}
}