题目大意
对于任何正整数x,其约数的个数记作g(x)。如果某个正整数x满足对任意的0<i<x,都有g(x)>g(i)
,则称x为反质数。现在给定一个数N,求出不超过N的最大的反质数。(N<=2*1e9)
总体思路
性质1
不超过N的最大的反质数x为不超过N的约数个数最多的数字中最小的数字(最小的数字保证了不存在一个比x小的数y使得g(x)==g(y))。
直接爆搜寻找x会超时,我们要想办法缩小数据范围。
定理(性质2的基础)
任何一个合数都会分解为若干个质数的整数幂的乘积所表示。
所有因数的个数为所有质数幂的次数加一的乘积。(证明:因数也是由质数的乘积所组成的,所以把质数的幂拆解成一个个质数,因数的个数就是那若干个质数的组合。因为一种质数如果次数为0也是一种情况,所以要加1)。
性质2
若x是约数最多的数字中最小的数字,则一个必要条件是用于表示它的若干个质数的整数幂的次数随质数大小的增加而递减(否则,如果存在质数p1<p2,其次数a1<a2,那么把a1,a2交换,因数的个数没变,但所得的积变小了,这与x是约数最多的数字中 最小的 数字相矛盾)。
因此,我们用Dfs枚举满足性质2的质数的整数幂的次数的排列方式,在所有的排列方式中选取最优值即可。因为有了一个必要条件,搜索的范围减小了,这可以使时间效率提高。
那么到底枚举多少个质数呢?
性质3.1
N<=2*1e9时,枚举的质数不超过10个。(因为前十一个质数的乘积已经超过2*1e9了)
质数的选择式连续的,证明与性质2相似。
性质3.2
每个质数幂的次数不超过31(2^31已经超过整数的范围了)。
深度搜索
所有的深度搜索都是在隐式图上进行的。每个结点有它的【值】,每个结点有它所连着的【边】。
- 本题中【边】是下一个质数的【幂次】的可能选项,其边权为 ak【下一个质数】^【幂次】,
- 节点的【值】是多元组:①当前处理的【数值】(所有质数幂的次数加一的乘积),②当前的【约数的个数】,③当前节点的深度(对应全局【质数数组】的下标),④每个节点根据性质2对边有一个限制,作为【剪枝】。
- 【边】和【节点】的关系为:乘积。
对于【隐式图】而言,节点的构成是通过DFS各个参数来传递和表示的。
非常重要的是对【叶子】的判定,因为【解】空间是由【叶子】构成的。可以根据【树深度】,也可以根据【孩子】的数目来判定。
每一个节点的性质应当是它自己的性质,而不是什么别的节点的性质。节点应当有一定的独立性。因此,DFS时,尽量不要出现prev什么什么的字眼。
注意事项
- 21不是质数。。。
- DFS初值的timeLimit应当为31,而不是正无穷。否则,枚举的幂的次数就成了2*1e31,就超时了。
- 凡是带有product(积)字眼的必须是long long,否则会越界。
#include <cstdio> #include <cstring> #include <cstdarg> #include <cstdlib> using namespace std; int MaxFactorCnt; int AnsProduct; long long N; const int Primes[15] = { 1,2,3,5,7,11,13,17,19,23,29,31 }; void Dfs(int p, int factorCnt, int timeLimit, long long totProduct) { if (p == 12) { if (factorCnt > MaxFactorCnt) { MaxFactorCnt = factorCnt; AnsProduct = totProduct; } else if (factorCnt == MaxFactorCnt && totProduct < AnsProduct) { AnsProduct = totProduct; MaxFactorCnt = factorCnt; } return; } long long nextPower = 1; for(int nextTime=0; nextTime<=timeLimit && totProduct*nextPower<=N; nextTime++) { Dfs(p + 1, factorCnt * (nextTime + 1), nextTime, totProduct*nextPower); nextPower *= Primes[p+1]; } } int main() { MaxFactorCnt = 1; AnsProduct = 1; scanf("%lld", &N); Dfs(0, 1, 31, 1); printf("%d ", AnsProduct); return 0; }