CodeForces 55D "Beautiful numbers"(数位DP+离散化处理)
参考资料:
[1]:CodeForces 55D Beautiful numbers(数位dp&&离散化)
题意:
求一个区间内的Beautiful numbers有多少个。Beautiful numbers指:一个数能整除所有组成它的非0数字。
例如15可以被1和5整除,所以15是Beautiful numbers。
我的理解:
起初,我先定义一个三维数组 dp[ i ][ j ][ k ]:来到 i 位置时,所有非零数的lcm = j,当前数位 k 时含有的 Beautiful numbers 的个数。
但是,由题意得,当前的数 k 可以是个很大的数(9e18),数组根本就开不下,那怎么办呢?
将当前的数 hash 一下,如何hash呢?
假设 a,b,c,d 为[0,9]的数,那么不存在另一个 a',b',c',d'
使得 a*11+b*13+c*17+d*19 = a' *11+b' *13+c' *17+d' *19;
也就是说,这 19 位数分别乘以大于10 的互不相等的前19个素数是不会存在重数的;
那么,hash完后,最大的数才7000多,那么开个 dp[20][50][8000] 的数组绰绰有余;
但问题来了,既然每个数的 hash 值都不想等,那,哪来的记忆化搜索?
所以说,这就是赤裸裸的暴力!!!!!!
so,举足无措,只好参考大佬博客辽。
下面具体说说我的理解:
首先解释一下上述博客中给出的公式 sum%(x*n)%x == sum%x 的证法以及作用;
证明:
sum%(x*n) = sum-(int)(sum / (x*n) )*(x*n);
sum%(x*n)%x = [sum-(int)(sum / (x*n) )*(x*n) ] % x = sum%x - 0 = sum % x;
那,接下来就要看看要怎么用这个公式了,预处理出 1~9的所有不同组合的 lcm ,我是用DFS预处理的;
参考代码如下:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 #define mem(a,b) memset(a,b,sizeof(a)) 7 8 int a[48]; 9 bool vis[10];//vis[i]:判断数字 i 是否访问过 10 bool vis2[2*3*4*5*6*7*8*9];//vis2[lcm]:判断当前的lcm是否在之前出现过 11 12 int GCD(int a,int b) 13 { 14 return a==0 ? b:GCD(b%a,a); 15 } 16 int LCM(int a,int b) 17 { 18 return a*b/GCD(a,b); 19 } 20 21 void DFS(int t,int lcm,int &k) 22 { 23 if(!vis2[lcm]) 24 { 25 a[k++]=lcm; 26 vis2[lcm]=true; 27 } 28 29 for(int i=1;i <= 9;++i) 30 { 31 if(vis[i]) 32 continue; 33 vis[i]=true; 34 DFS(t+1,LCM(lcm,i),k); 35 vis[i]=false; 36 } 37 } 38 int main() 39 { 40 int k=0; 41 42 mem(vis,false); 43 mem(vis2,false); 44 45 DFS(1,1,k); 46 sort(a,a+48); 47 for(int i=0;i < k;++i) 48 printf("%d,",a[i]); 49 50 return 0; 51 }
与处理完后,你会发现最大的lcm为2520,且 2520%(其余的lcm) = 0;
那么,这就好办了,对于当前所形成的数 curNum 和当前的 lcm,我们需要判断 curNum % lcm 是否等于0,那么问题就是
curNum 太大了怎么办?
通过上面的公式,可知 curNum%lcm = curNum%(lcm*(2520/lcm) )%lcm = (curNum%2520)%lcm;
所以,每次只需要将 curNum%2520 即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 #define ll long long 7 #define mem(a,b) memset(a,b,sizeof(a)) 8 9 ll n,m; 10 int digit[20]; 11 ll dp[20][50][2520]; 12 int a[48]={1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10, 13 12,14,15,18,20,21,24,28,30,35, 14 36,40,42,45,56,60,63,70,72,84, 15 90,105,120,126,140,168,180,210, 16 252,280,315,360,420,504,630,840,1260,2520}; 17 18 int GCD(int a,int b) 19 { 20 return a==0 ? b:GCD(b%a,a); 21 } 22 int LCM(int a,int b) 23 { 24 return a*b/GCD(a,b); 25 } 26 27 ll DFS(int curPos,int curNum,int lcm,bool limit) 28 { 29 if(curPos == -1) 30 return curNum%lcm == 0 ? 1:0; 31 int t=lower_bound(a,a+48,lcm)-a; 32 if(!limit && dp[curPos][t][curNum] != -1) 33 return dp[curPos][t][curNum]; 34 35 int up=limit ? digit[curPos]:9; 36 ll ans=0; 37 for(int i=0;i <= up;++i) 38 ans += DFS(curPos-1,(curNum*10+i)%2520,(i != 0 ? LCM(lcm,i):lcm),limit&&i==digit[curPos]); 39 if(!limit) 40 dp[curPos][t][curNum]=ans; 41 42 return ans; 43 } 44 ll Solve(ll x) 45 { 46 int k=0; 47 while(x) 48 { 49 digit[k++]=x%10; 50 x /= 10; 51 } 52 return DFS(k-1,0,1,true); 53 } 54 int main() 55 { 56 int test; 57 scanf("%d",&test); 58 mem(dp,-1); 59 while(test--) 60 { 61 scanf("%lld%lld",&n,&m); 62 printf("%lld\n",Solve(m)-Solve(n-1)); 63 } 64 return 0; 65 }