题解:P8778 [蓝桥杯 2022 省 A] 数的拆分

Luogu - P8778 数的拆分

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=x1y1x2y2(x1,y1N+, y1,y22) 的形式。

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} T105,ai1018,直接看有没有 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(n109) 的数后剩下的 a i a_i ai 一定是一个完全平方数;分解质因数时筛掉 ≤ m ( m ≤ 1 0 6 ) \le m(m \le 10^6) m(m106) 的数后剩下的 a i a_i ai 一定是一个完全立方数

可知 m ≤ a i 3 m \le \sqrt[3]{a_i} m3ai ,即 m ≤ 1 0 6 m \le 10^6 m106,则又可以枚举 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 106109 这个区间里的质因子就最多有 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=1Tai ),可以通过。

Code

注意系统自带的 sqrt 函数和 cbrt 函数可能存在精度误差(见 P10373立方根 题目背景)。

在 P10373 中给出了修复方法,但那不是很准确。发现整型向下取整误差一定不超过 1 1 1,所以可以把分解完质因数后的 a i a_i ai 分别尝试 a i ± 1 a_i \pm 1 ai±1 是否可行,以此修复误差。

#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,谢谢大家!。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 如果一个的十进制表示法中,任何相邻的两个字均不相同,那么这个就被称为 “有趣的”。例如,以下各都是有趣的: 1、21、321、4321 但是,以下各却不是有趣的: 22、121、3212 给出正整 n,请你求出长度为 n 的有趣的的个,并输出这个值除以 998244353 的余。 输入格式 输入一行,包含一个整 n。 输出格式 输出一个整,表示答案除以 998244353 的余据范围 1≤n≤1000 输入样例1: 2 输出样例1: 81 输入样例2: 3 输出样例2: 531441 题解 有趣的 求长度为n的有趣的个 思路: 第一位有9种选择,第二位有9种选择,第三位有8种选择(因为只有和前一位不一样才可以) 代码1: 时间复杂度 O(n) C++ 代码 #include <iostream> #include <cstdio> using namespace std; int main() { int n; scanf("%d", &n); int mod = 998244353; long long res = 1; for (int i = 1; i <= n; i ++ ) res = res * (i <= 2 ? 9 : 10 - i % 2) % mod; printf("%lld\n", res); return 0; } 代码2: 时间复杂度 O(n) C++ 代码 #include <iostream> #include <cstdio> using namespace std; const int MOD = 998244353; int main() { int n; scanf("%d", &n); int f[1010][10]; for (int i = 0; i < 10; i ++ ) f[1][i] = 1; for (int i = 2; i <= n; i ++ ) for (int j = 0; j < 10; j ++ ) for (int k = 0; k < 10; k ++ ) if (j != k) f[i][j] = (f[i][j] + f[i - 1][k]) % MOD; int res = 0; for (int i = 1; i < 10; i ++ ) res = (res + f[n][i]) % MOD; printf("%d\n", res); return 0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值