[POI2002][HAOI2007] 反素数
题面
题解
小蓝书 + 自己的补充
我们知道:
如果一个数 \(m = p_{1} ^ {k_1} * p_{2} ^ {k_2} * … *p_n ^ {k_n}\)。
那么它的约数个数等于 \(\prod_{i = 1} ^ {n} k_i + 1\)
所以我们可以通过搜索由小到大确定每个质因数的指数,计算数值和约数值来更新答案,但普通的搜索肯定是不行的,所以我们需要剪枝。
引理1
1 ~ N 中最大的反素数,就是 1 ~ N 中约数个数最多的数中最小的数。
证明:
- 约数个数最多保证这个数具有反素数的潜质。
- 约数个数最多的数中最小的数,保证了比它小的数约数个数都比它小,保证了它是反素数,比它大的数中,没有约数个数严格比它大的数,保证了不会有更大的数是反素数。
引理2
1 ~ N 中任何数的不同质因子都不会超过 10 个,且所有的质因子的指数总和不会超过 30。
证明:
- 最小的 10 个质数的乘积大于了 2e9,显然可以用前面的小质数来替换后面的大质数使数在约数个数相同的情况下更小,是这个数能够在 N 的范围内能继续容纳下更多的质数,使约数个数更多。
- \(2^{31} > 2e9\)。
引理3
将 x 分解质因数后写作:
\(2^{c_1} * 3^{c_2} * 5^{c_3} * 7^{c_4} * 11^{c_5} * 13^{c_6} * 17^{c_7} * 19^{c_8} * 23^{c_9} * 29^{c_{10}}\)。
且 \(c_1 \geq c_2 \geq … \geq c_10 \geq 0\)。
证明:
如果有 \(c_i < c_j\ 且\ i < j\),那么显然可以通过引理2的证明的操作,交换它们的指数,使答案具有成为更优的潜力。
代码
通过上述三个引理,我们已经得到了三个强有力的剪枝,因此我们只需要由小到大确定前十个质数的指数,满足指数单调,利用引理1来更新答案即可,搜索量是比较小的,实测也跑的很快。
#include<cstdio>
const int p[15] = {
2,3,5,7,11,13,
17,19,23,29,31,
};
int n,ans = 0x3f3f3f3f,ans_cnt = 0;
void dfs(int pre,int i,int val,int q,int cnt) {
if(pre == 0 || i > 9) return ;
if(cnt > ans_cnt) ans = val,ans_cnt = cnt;
if(cnt == ans_cnt && val < ans) ans = val,ans_cnt = cnt;
if(1ll * val * p[i] <= n && q < pre) dfs(pre,i,val * p[i],q + 1,cnt / (q + 1) * (q + 2));
else {
int res = val;
for(int j = 0; j <= q; j++,res /= p[i])
if(1ll * res * p[i + 1] <= n) dfs(q - j,i + 1,res * p[i + 1],1,cnt / (q + 1) * (q - j + 1) * 2);
}
}
int main() {
scanf("%d",&n);
dfs(0x3f3f3f3f,0,1,0,1);
printf("%d",ans);
return 0;
}

浙公网安备 33010602011771号