[AHOI 2018初中组] 根式化简 题解
**[AHOI 2018初中组] 根式化简 题解 **
好了告诉你们其实这就是一题非常简单的数论……
先考虑最暴力的算法
首先可以明了的一点就是如果我们把一个数 \(N\) 表示成 \(x=k*a^3\) 的形式,那么 \(k\) 一定满足 \(k ≤ \sqrt[3]{N}\)。
所以我们可以暴力枚举 \(1- \sqrt[3]{N}\) 的每一个数 \(x\),只要满足 \(x | N\) ,我们就判断 \(N/x\)是否为立方数,这些立方数可以在 \(\sqrt[3]{N}\)的时间内预处理出来。
我们看看数据范围,好的你已经 \(get\) \(1-4\) 个测试点,也就是 \(40\) 分了。
有没有优化?
我们注意到 \(5,6\) 两个特殊数据点:\(x\) 为立方数,这启示我们可以二分处理掉这一部分数据。
很明显若 \(i\) 是递增的,那么 \(i^3\) 也是递增的,所以答案具有单调性。
并且 \(\sqrt[4]{N} ≤ \sqrt[3]{N} ≤ \sqrt{N}\) ,所以二分下界 $ \sqrt{N}$,上界不好取,取 \(\sqrt[3]{10^{18}}=10^6\) 即可。
按照数据打程序,你已经可以通过 \(1-6\) 个测试点了。\(60\) 分在没想到正解的时候还是很可观的。
总结启示,得到正解
你知道 \(N=k*a^3\) ,那么应该会有:\(k,a≤\sqrt[3]{N}\) 对吧?那么我们肯定不能暴力对 \(N\) 质因数分解,所以我们想想这些因数在什么范围内才是我们所要的?最简单的上界也是很容易想到的:\(\sqrt[3]{N}\)。看一眼数据,唔!又过了两个点\(QAQ\)。
单着并不是我们要的,我们在期望正解。很显然 \(\sqrt[3]{N}\) 这个上界还是太大,因此我们需要观察还有没有更小的上界。其实我们发现可以把上界缩小到 \(\sqrt[4]{N}\),然后$ \forall i∈[1,\sqrt[4]{N}],\text{且}i∈prime,i\mid N $,把 \(N\) 中 \(i\) 全部除去,并且只要除的次数每逢 \(3\) 的倍数就把 \(ans\) 累乘上 \(i\)。
为什么这样可行?这么做极有可能剩下一个较大的 \(k\) 还可以继续分解啊?
其实是不可能的,如果剩下除完了的 \(N\) 还能继续分解成上面那种形式的话,那么它早就被 \([2,\sqrt[4]{N}]\) 内的质因子除掉了,除非剩下了一个立方数。
所以我们再次利用二分判断剩下的除完的那个 \(N\) 还是不是立方数就好了,如果是累乘上 \(\sqrt[3]{N}\)。
接下来根据代码来理解思路吧:
#include<bits/stdc++.h>
using namespace std;
const int N = 4e4 + 10;
const int M = 1e6 + 10;
#define ll long long
int prime[M]; bool v[M];
int Prime(int n) {
int m = 0;
v[0] = v[1] = 1;
for(int i = 2; i <= n; i++) {
if(v[i] == 0) prime[++m] = i;
for(int j = 1; j <= m && prime[j] <= n / i; j++) {
v[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
return m;
}
ll p[M];
int main() {
// 预处理立方根
for(int i = 1; i <= M; i++) p[i] = (ll) i * i * i;
// 预处理质数
int m = Prime(N), T; ll x, ans = 1;
scanf("%d", &T);
while(T--) {
scanf("%lld", &x);
// 在 1-x^(1/4) 内枚举因子
for(int i = 1, cnt = 0; i <= m && p[i] <= x; i++, cnt = 0)
while(x % prime[i] == 0) {
cnt++, x /= prime[i];
if(cnt % 3 == 0) ans *= prime[i];
}
// 特判剩下的数
ll k = lower_bound(p + 1, p + M + 1, x) - p;
if(k * k * k == x) ans *= k;
printf("%lld\n", ans);
ans = 1;
}
return 0;
}