//真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]会更大,搜索起来更快。(当然,这只是小优化)。
不会写的话看代码。
假设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 ",dfs(n,0)); //printf("I64d printed "); 44 return 0; 45 } 46 /* 47 20 3 48 2 3 5 49 50 50 2 51 15 8 52 */