[Acwing4319] 合适数对

[Acwing4319] 合适数对

  • 数论算法 同余

给定一个长度为 n 的正整数数列 a1,a2,,an 和一个正整数 k

请你判断共有多少个数对 (l,r) 同时满足:

  • 1l<rn
  • 存在一个整数 x 使得 al×ar=xk 成立

输入格式

第一行包含两个整数 n,k

第二行包含 n 个正整数 a1,a2,,an

输出格式

一个整数,表示满足条件的数对的数量。

数据范围

前三个测试点满足 2n10
所有测试点满足 2n105,2k100,1ai105

Acwing上的一道Hard难度的周赛题。

首先我们考察这个条件:存在一个整数 x 使得 al×ar=xk 成立。注意到 al,ar>0,因此不妨认为 x 是正整数。

由算术基本定理,任意一个正整数都能唯一地分解为全体质数的幂积:

N=pimi,N>1,pi,mi

于是,我们可以根据上式将任意一个正整数表示为一个无限维行向量:

N=(m1,m2,m3,)

例如,42=213171=2131507190,那么,42=(1,1,0,1,0,0,0,)。特别地 1=(0,0,0,)

那么,任意两个正整数的乘法运算就可以转换为向量加法运算:

a×b=a+b

于是我们知道 xk=kx,向量 xk 的每一个元素都能被 k 整除

那么,我们需要找出的满足 al×ar=xk 的正整数 al,ar满足性质 : alark

注意到我们并不关心al+ar的每个元素的具体值,只关心它们是否都能被 k 整除。那么,接下来就应该考虑什么情况下两数之和能被 k 整除。

由同余代数的知识可以知道:k 的任意整数倍模 k 都是 0,如果 acmodkbdmodk,那么 a+bc+dmodk,那么,如果要有 a+b 能被 k 整除,一定有 c+d0modk

ddmodk,那么 cdmodk,即 cdmodk

于是,如果我们知道一个数 d ,那么所有与 d 加起来能被 k 整除的数都与 dmodk 同余

于是乎,所有与 a 加起来后每一个元素能被 k 整除的向量 b,一定满足:

bi=aimodk,biaiba

于是,我们就有了解题思路:

  1. 从前往后枚举每一个元素 ai,利用分解质因数算法计算 ai 的向量,求出该向量每个元素模 k 后的向量 Aik 和每个元素取负后再模 k 的向量 (Ai)k

  2. 在一个记录向量出现次数的数据结构中寻找向量 (Ai)k 的出现次数,将它累加到答案

  3. (Ai)k 记录到数据结构,即其出现次数加1

注意到计算向量会有些复杂,因为我们需要知道每个质数在向量中的位置,由于一个正整数和它的向量是唯一对应的,于是在分解质因数时,我们只计算向量 (Ai)k(Ai)k 对应的正整数值 (ai)k(ai)k即可。

由于每个质数因子的指数取模后一定不会变大,一定有 (ai)kai 。而取负后再取模就很可能变得更大,比如 1mod100=99,因此 (ai)k 有可能大于 105,这种情况下一定没有 (aj)k=(ai)k,要注意判断。

分解质因数的时间复杂度为 O(n) ~ O(logn) ,总时间复杂度为 O(nn) ~ O(nlogn)

#include<iostream>
#include<unordered_map>
#include<string>
#include<vector>
#include<cmath>
using namespace std;
typedef unsigned long long ULL;
typedef pair<ULL,ULL> PUU;

const int N = 100010;

PUU K_num_anti(int x, int k){
    PUU ans={1,1};
    for(int i = 2;i<=x/i;i++){
        if(x%i == 0){
            int t = 0;
            while(x%i == 0){
                t++;
                x/=i;
            }
            //求幂
            for(int j = 1;j<= (t%k);j++){
                ans.first *= i;
            }
            for(int j = 1;j<=((-t)%k+k)%k;j++){
                if(ans.second>N){ans.second = N;break;}
                ans.second*= i;
            }
        }
    }
    if(x>1){
        ans.first *= x;
        for(int i = 1;i<=((-1)%k+k)%k;i++){
            if(ans.second>N){ans.second = N;break;}
            ans.second*= x;
        }
    }
    return ans;
}

unordered_map<ULL,int> fd;
int p[N];
int main(){
    int n,k;
    scanf("%d%d", &n,&k);
    for(int i = 1;i<=n;i++)scanf("%d", p+i);
    
    ULL ans = 0;

    for(int i = 1;i<=n;i++){
        int num = p[i];
        // res 第一个存[ai]k,第二个存 [-ai]k,如果它是大于N的,存N
        auto res = K_num_anti(num,k);
        
        if(res.second!=N && fd.find(res.second)!=fd.end())ans += fd[res.second];
        if(fd.find(res.first) != fd.end()) fd[res.first]++;
        else fd.emplace(res.first,1);
    }
    printf("%lld\n",ans);
    return 0;
}
posted @   Sarfish  阅读(86)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示