#51 D. Beautiful numbers (数位dp+离散化)

来源:互联网 发布:网络开展学生教育工作 编辑:程序博客网 时间:2024/06/05 11:05

题目链接:

点击打开链接

http://codeforces.com/contest/55/problem/D

题意:

定义:Beautiful Numbers : 这个数能整除它的所有位上非零整数。问你[ l , r ] 之间的Beautiful Numbers的个数。

题解:

数位dp。

如果一个数能整除它的所有的非零数位,那么相当于它能整除个位数的最小公倍数。因此记忆化搜索中的参数除了len(当前位)和flag(是否达到上界),有一个pre_lcm表示前面的数的最小公倍数,判断这个数是否是Beautiful Numbers,还要有一个参数表示前面数,但是这个数太大,需要缩小它的范围。

解决方法:

缩小前面组成的数的范围。
可以发现所有个位数的最小公倍数是2520,假设当前的Beautiful Numbers是x,
那么 x % lcm{ dig[ i ] } = 0,
又 2520%lcm{ dig[ i ] } = 0。
那么x%2520%lcm{ dig[i] } = 0,x范围由9*10^18变为2520。


经过分析后可以设出dp[20][2520][2520],dp[ i ] [ j ][ k ]表示处理到i位,前面的数的最小公倍数为j,前面的数%2520为k。这样做居然会TLE...明显爆内存...

因为1~9组成的最小公倍数只有48个,1 ,2, 3, 4, 5, 6, 7, 8 ,9 ,10 ,12 ,14 ,15 ,18, 20, 21 ,24, 28, 30 ,35, 36, 40 ,42, 45, 56, 60, 63, 70 ,72 ,84 ,90 ,105 ,120, 126, 140 ,168 ,180 ,210, 252, 280, 315 ,360, 420, 504 ,630 ,840, 1260 ,2520。一共48个。 可以离散化,这样数组就降到了dp[20][50][2520]。


AC代码:

#include<bits/stdc++.h>using namespace std;typedef long long ll;const int max_lcm=2520;int dig[25];int Hash[2525];ll dp[25][50][2525];ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}ll lcm(ll a,ll b){return a/gcd(a,b)*b;}ll dfs(int len,int pre_lcm,int pre_num,int flag){if(len==0){return pre_num % pre_lcm ==0;}if(!flag && dp[len][ Hash[pre_lcm] ][pre_num]!= -1 )return dp[len][ Hash[pre_lcm] ][pre_num];int n = flag ? dig[len] : 9;ll res = 0;for(int i=0;i<=n;i++){int now_num = (pre_num * 10 +i) % max_lcm;int now_lcm = pre_lcm;if(i) now_lcm = lcm(pre_lcm,i);res += dfs(len-1,now_lcm,now_num,flag&&i==n);}if(!flag){dp[len][Hash[pre_lcm]][pre_num] = res;}return res;}ll cal(ll num){int len=0;while(num){dig[++len]=num%10;num/=10;}return dfs(len,1,0,1);}int main()  {  //freopen("in.txt","r",stdin);int t;ll a,b;int cnt=0;for(int i=1;i<=2520;i++){if(max_lcm%i==0){Hash[i]=++cnt;}}cin>>t;memset(dp,-1,sizeof(dp));while(t--){scanf("%I64d %I64d",&a,&b);//cout<<cal(b)-cal(a-1)<<endl;printf("%I64d\n",cal(b)-cal(a-1));}    return 0;  }  




1 0
原创粉丝点击