cdoj Dividing Numbers 乱搞记忆化搜索

//真tm是乱搞 但是(乱搞的)思想很重要

解:大概就是记忆化搜索,但是原数据范围太大,不可能记下所有的情况的答案,于是我们就在记下小范围内的答案,当dfs落入这个记忆范围后,就不进一步搜索,直接返回记下来的答案,这样就起到了优化的效果,但是并不知道这种复杂度是怎么算的。然而我们由大到小排序,使得状态总可以很快地落入记忆化的范围。

dfs(n,now)代表[1..n]内不会被a[now]...a[k-1]整除的数有多少,那么答案就是dfs(n,0)。

转移关系如下:dfs(n,now)=dfs(n,now+1)-dfs(n/a[i],now+1)

之所以要-dfs(n/a[i],now+1)是为了避免一个数被重复减多次,能被整除的数整除后按大小排列一定是1,2,3,...,n/a[i],如果能被后面的数整除,就将其减去,也就是减dfs(n/a[i],now+1).

 

/*

某学长的讲解:

 给K个两两互素的数,问[1,N]中有多少个数不被K个数里任何一个数整除。

假设N比较小,可以这样做。dp[i][j]表示前i个素数,范围在[1,j]里的答案。那么,方程转移dp[i][j]=dp[i-1][j]+dp[i-1][j/p[i]]。(具体为啥仔细想想素数的性质,或者按照分解素数方法来想)

N很大,怎么办。当N<10万,20万,30万可以直接dp做。当N很大的时候,注意到j/p[i],是log级别的,情况数并不会很多。所以可以设一个M,比如M=20万,当j<=M的时候,记忆化,O(1)查询,当j>M的时候,dfs搜索。

这题不好分析复杂度。可以发现,P数组里元素顺序是不影响答案的,我们可以把P从小到大排序,这样j/p[i]中的p[i]会更大,搜索起来更快。(当然,这只是小优化)。

不会写的话看代码。

*/

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cmath>
 4 #include<algorithm>
 5 #include<cstring>
 6 #include<cstdlib>
 7 #include<queue>
 8 #include<vector>
 9 #include<map>
10 #include<stack>
11 #include<string>
12 
13 using namespace std;
14 
15 const int MAXN=23333;
16 
17 long long n;
18 int k;
19 int a[107];
20 long long f[MAXN][107];
21 
22 bool cmp(int a,int b){
23     return a>b;
24 }
25 
26 long long dfs(long long n,int now){
27     if (now>=k || n==0) return n;
28     if (n<MAXN && f[n][now]>=0) return f[n][now];
29     long long tmp=dfs(n,now+1)-dfs(n/a[now],now+1);
30     if (n<MAXN) f[n][now]=tmp;
31     return tmp;
32 }
33 
34 int main(){
35     scanf("%lld%d",&n,&k);
36     for (int i=0;i<MAXN;i++){
37             for (int j=0;j<k;j++){
38                     f[i][j]=-1;
39             }
40     }
41     for (int i=0;i<k;i++) scanf("%d",&a[i]);
42     sort(a,a+k,cmp);
43     printf("%lld\n",dfs(n,0)); //printf("I64d printed\n");
44     return 0;
45 }
46 /*
47 20 3
48 2 3 5
49 
50 50 2
51 15 8
52 */
View Code

 

posted @ 2015-05-31 15:18  鼠宝宝  阅读(181)  评论(0编辑  收藏  举报