Begining
感觉这题评黄少了,至少得是道绿题。
这篇题解记录了我一下午做题的心路历程,可能有点兀长,也有很多没用的内容,但或许可以加深对题目的理解。
有错误欢迎指出!本蒟蒻一定虚心接受。
提示:题解中有 4 个结论,代表 4 个可行的判定,是有连贯关系的,可以着重理解。
Description
给定 T T T 个正整数 a i a_{i} ai,分别问每个 a i a_{i} ai 能否表示为 a i = x 1 y 1 ⋅ x 2 y 2 ( x 1 , y 1 ∈ N + , y 1 , y 2 ≥ 2 ) a_i={x_1}^{y_1} \cdot {x_2}^{y_2}(x_1,y1 \in \mathbb{N^+},\ y1,y2 \ge 2) ai=x1y1⋅x2y2(x1,y1∈N+, y1,y2≥2) 的形式。
Analysis
首先对 a i a_i ai 进行质因子分解: a i = p 1 c 1 × p 2 c 2 × ⋯ a_i={p_1}^{c_1} \times {p_2}^{c_2} \times \cdots ai=p1c1×p2c2×⋯
然后,我们只需要考虑把 a i a_i ai 的幂次拆成一个 y 1 y_1 y1 的倍数加上 y 2 y_2 y2 的倍数的形式,即对于 a i = p y a_i=p^y ai=py, y = k 1 × y 1 + k 2 × y 2 y = k_1 \times y_1 + k_2 \times y_2 y=k1×y1+k2×y2。
以 5 7 5^7 57 为例,它可以拆成 ( 5 2 ) 2 × ( 5 1 ) 3 (5^2)^2 \times (5^1)^3 (52)2×(51)3,也就是 5 2 × 2 × 5 1 × 3 5^{2 \times 2} \times 5^{1 \times 3} 52×2×51×3,而 2 × 2 + 1 × 3 = 7 2 \times 2 + 1 \times 3=7 2×2+1×3=7。
那么我们只要合理构造出 y 1 y_1 y1 和 y 2 y_2 y2 就行了。在刹那间,你的脑电波和大神欧拉对上了,他给你一条重要信息: 2 , 3 2,3 2,3 是一对万能构造数!
即我们一定可以把 a i a_i ai 的幂次拆成 2 k 1 + 3 k 2 2k_1+3k_2 2k1+3k2 的形式。
简单证明一下:
对于所有偶数,因为偶数一定是 2 2 2 的倍数,所以只要把 k 2 k_2 k2 的系数设为 0 0 0 即可;
对于所有 ≥ 3 \ge 3 ≥3 的奇数,因为奇数 = 偶数 + 奇数,而相邻的奇数之间的最小差值为 2 2 2,所以用 2 k 1 2k_1 2k1 表示偶数部分, 3 k 2 3k_2 3k2 表示奇数部分,一定可以凑成一个奇数。
凑不出来的只有 1 1 1,但这道题里面没有 1 1 1,所以该结论成立。
结论 1:
拆分 a i a_i ai 的质因子一定不能有 1 1 1 次幂,这一定凑不出来。
这条结论非常重要,直接决定了我们以后的做题方向。But, T ≤ 1 0 5 , a i ≤ 1 0 18 T \le 10^5,a_i \le 10^{18} T≤105,ai≤1018,直接看有没有 1 1 1 次幂会超时。
那么,我们可以先判断 a i a_i ai 是不是完全平方数或完全立方数,如果是,那么它的幂次一定可以拆成 2 k 1 2k_1 2k1 或 3 k 2 3k_2 3k2 的形式。
若 ⌊ a i ⌋ × ⌊ a i ⌋ = a i \lfloor \sqrt{a_i} \rfloor \times \lfloor \sqrt{a_i} \rfloor = a_i ⌊ai ⌋×⌊ai ⌋=ai,则 a i a_i ai 是完全平方数;
若 ⌊ a i 3 ⌋ × ⌊ a i 3 ⌋ × ⌊ a i 3 ⌋ = a i \lfloor \sqrt[3]{a_i} \rfloor \times \lfloor \sqrt[3]{a_i} \rfloor \times \lfloor \sqrt[3]{a_i} \rfloor = a_i ⌊3ai ⌋×⌊3ai ⌋×⌊3ai ⌋=ai,则 a i a_i ai 是完全立方数。
(这样投机取巧应该也能骗到不少分)
然后,因为现在 a i a_i ai 既不是完全平方数,也不是完全立方数,那么其一定可以表示成 n 2 × m 3 n^2 \times m^3 n2×m3 的形式,其中 n , m > 1 n,m \gt 1 n,m>1。
那它又一定可以转化为 ( n × m ) 2 × m (n \times m)^2 \times m (n×m)2×m。
结论 2:于是乎,我们又有了一个重大发现:
若 a i = n 2 × m 3 a_i=n^2 \times m^3 ai=n2×m3,则分解质因数时筛掉 ≤ n ( n ≤ 1 0 9 ) \le n(n \le 10^9) ≤n(n≤109) 的数后剩下的 a i a_i ai 一定是一个完全平方数 ;分解质因数时筛掉 ≤ m ( m ≤ 1 0 6 ) \le m(m \le 10^6) ≤m(m≤106) 的数后剩下的 a i a_i ai 一定是一个完全立方数!
可知 m ≤ a i 3 m \le \sqrt[3]{a_i} m≤3ai ,即 m ≤ 1 0 6 m \le 10^6 m≤106,则又可以枚举 m m m ,这样枚举立方以下的,又能降一点时间复杂度。
But,这样复杂度为 O ( T m ) \text{O}(Tm) O(Tm),超时在所难免。
题目做到这里,基本上人已经懵了。但有句话说得好:"不忘初心,牢记使命!"我们的"初心"是什么?------是找 a i a_i ai 分解质因数后有没有一次方的质因数。我们的"使命"是什么?------是 AC 这道题。
所以,我们不能就此放弃。回想我们最初的目的是找有没有一次方的质因数,那我们就可以把 a i 3 \sqrt[3]{a_i} 3ai 以内(即 1 0 6 10^6 106 以内)的质数先筛出来。
那它剩下的质因数一定都是 > 1 0 6 \gt 10^6 >106 的数,大于 a i = 1 0 9 \sqrt{a_i}=10^9 ai =109 的质因数一定最多只有 1 1 1 个(它自然是一次的),那么在 1 0 6 ∼ 1 0 9 10^6 \sim 10^9 106∼109 这个区间里的质因子就最多有 2 2 2 个。
结论 3:
如果有 > 1 0 9 \gt 10^9 >109 的质因子,那么它一定是一次方的,这就找到了。
那除了上述之外,还能怎么优化呢?
再回到最初的起点,是一个 n 2 × m 3 n^2 \times m^3 n2×m3 的情况,根据我们敏锐的数感,聪明的你会发现,这个 n n n 一定是很小的,因为过大的 n n n 一定组不成这种形式。
假如 n = m = 1000 n= m = 1000 n=m=1000,那么 100 0 2 × 100 0 3 = 1 0 15 1000^2 \times 1000^3=10^{15} 10002×10003=1015,已经很大了;当 n = m = 3000 n=m=3000 n=m=3000 时, 300 0 2 × 300 0 3 = 2.43 × 1 0 17 3000^2 \times 3000^3=2.43 \times 10^{17} 30002×30003=2.43×1017,已经十分接近。
所以 n = m = 4000 n=m=4000 n=m=4000 的时候, n 2 × m 3 n^2 \times m^3 n2×m3 一定 > 1 0 18 \gt 10^{18} >1018。
结论 4:
n 2 × m 3 n^2 \times m^3 n2×m3 中 n , m n,m n,m 最多只有 4000 4000 4000。
正解:
综上,只要去枚举 4000 4000 4000 以内的质数(结论 4),看里面有没有是一次方的质因数(结论 1);同时把质因数给 a i a_i ai 除进去,看最后剩下的 a i a_i ai 是不是完全平方数或完全立方数即可(结论 2)。
这样去枚举,则不算预处理素数的复杂度是 O ( T ∑ i = 1 T a i ) \text{O}(T \sum\limits_{i=1}^{T} \sqrt{a_i}) O(Ti=1∑Tai ),可以通过。
Code
注意系统自带的 sqrt
函数和 cbrt
函数可能存在精度误差(见 P10373立方根 题目背景)。
在 P10373 中给出了修复方法,但那不是很准确。发现整型向下取整误差一定不超过 1 1 1,所以可以把分解完质因数后的 a i a_i ai 分别尝试 a i ± 1 a_i \pm 1 ai±1 是否可行,以此修复误差。
cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxp = 1000;
int vis[maxp + 5], prime[maxp + 5], len;
// 用埃氏筛筛出1000以内的素数
void getPrime()
{
for(int i = 2; i <= maxp; i ++){
if(!vis[i]) prime[len ++] = i;
for(int j = 2 * i; j <= maxp; j += i) vis[j] = 1;
}
}
// 判断x是不是完全平方数或完全立方数
bool check(ll x)
{
ll sq = sqrtl(x);
if(sq*sq==x || (sq+1)*(sq+1)==x || (sq-1)*(sq-1)==x){ // 避免有误差,这个误差确定不超过1
return true;
}
ll cb = cbrtl(x);
if(cb*cb*cb==x || (cb+1)*(cb+1)*(cb+1)==x || (cb-1)*(cb-1)*(cb-1)==x){
return true;
}
return false;
}
void solve()
{
ll a;
cin >> a;
// 是完全平方数或完全立方数直接yes
if(check(a)){
cout << "yes\n";
return ;
}
// 下面分解质因数
bool flag = true;
for(int i = 0; i < len; i ++){
int cnt = 0;
int p = prime[i];
while(a % p == 0){
cnt ++;
a /= p;
}
// 出现1次方,不合法
if(cnt == 1){
cout << "no\n";
return ;
}
}
// 最后再判断一遍分解完的即可
if(check(a)) cout << "yes\n";
else cout << "no\n";
}
signed main()
{
ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
getPrime();
int T; cin >> T;
while(T --) solve();
return 0;
}
Ending
这里是 YLCHUP,谢谢大家!。