1. 问题描述:
给定一个长度为 n 的正整数数列 a1,a2,…,an 和一个正整数 k,请你判断共有多少个数对 (l,r) 同时满足:
1 ≤ l < r ≤ n;
存在一个整数 x 使得 al × ar = x ^ k 成立;
输入格式
第一行包含两个整数 n,k;第二行包含 n 个正整数 a1,a2,…,an
输出格式
一个整数,表示满足条件的数对的数量
数据范围
前三个测试点满足 2 ≤ n ≤ 10;
所有测试点满足 2 ≤ n ≤ 10 ^ 5,2 ≤ k ≤ 100,1 ≤ ai ≤ 10 ^ 5;
输入样例:
6 3
1 3 9 8 24 1
输出样例:
5
来源:https://www.acwing.com/problem/content/4322/
2. 思路分析:
分析题目可以知道这道题目考察的是算术基本定理:任何一个大于 1 的自然数 N,如果 N 不为质数,那么 N 可以唯一分解成有限个质数的乘积,如下图所示:其中 p1,p2,... pn 是质数,p1 < p2 < ... < pn,指数 ai 都是正整数:
N 是 k 次幂等价于 N 中的每一个指数 ai 都能够被 k 整除,也即每一个 ai 是 k 的倍数, 对于这道题目来说等价于判断任意给的两个数字 ai * aj 中所有质因子的次数能否被 k 整除,因为需要求解所有满足要求的数对所以首先需要考虑如何枚举才可以将所有答案枚举出来,我们可以考虑 aj,计算一下 aj 前面有多少个 ai 使得 ai * aj 分解质因数的结果中满足所有质因子的次数能够被 k 整除,首先需要对 aj 进行因式分解:
由于我们关心的是分解质因子之后的次数,也即质因子的次数除以 k 的余数,若 aj 前面的 ai 能够分解成下面的式子:
那么 ai * aj 能够被表示成 x ^ k 等价于每一个 (ri + ti) 能够被 k 整除,由于只是判断质因子的次数能否被 k 整除所以我们实际上在求解的过程中对于每一个 ri' = ri % k,ti' = ti % k,所以 1 <= ri' < k, 并且 1 <= ti' < k,所以 (ri' + ti') 能够被 k 整除等价于 ti' = k - ri'(ri',ti' 都小于 k 所以 ri' + ti' = k),对于当前枚举的 aj 如果有 x 项质因子的次数 ri' 都不能够被 k 整除,那么 ai 必然也是对应 x 项质因子的次数 k - ri' 不能够被 k 整除,并且两者不能够被 k 整除对应的质因子都是相等的,在枚举 aj 的时候我们需要快速找到所有与 aj 匹配的 ai,快速判断一个数字是否存在可以使用哈希表,而且对于这道题目来说只有当两者不能够被 k 整除的质因子相等并且质因子相加的次数等于 k 的时候才可以进行匹配,也即 aj 中不能够被 k 整除的质因子的次数对应的质因子与 ai 中对应的质因子也是一样的,这样 ai 与 aj 才可以进行匹配,两者质因子次数相加的结果等于 k,所以我们只需要在哈希表中维护 i ^ ri' 即可,也即维护一个整数,这样在枚举 aj 的时候判断之前是否存在 i ^ (k - ri') ,如果存在那么对应的次数就是与当前 aj 匹配的 ai 的个数,在枚举 aj 的时候将 aj 分解质因数之后不能够被 k 整除对应的的 i ^ ri' 的值存在到哈希表中即可。
3. 代码如下:
go:感觉 go 语言的 fmt.Scan 函数读取数据还是比较慢的,读取 10 ^ 5 的数据耗时还是比较久的,提交上去的运行时间比 python 还久,需要使用 bufio 包中的 bufio.NewReader() 结合 fmt.Fscan() 和 ftm.Fprintln() 函数优化输入输出这样读取和输出数据的速度才比较快:
package main
import "fmt"
// 计算a的b次幂
func power(a int, b int, N int) int {
res := 1
for i := 0; i < b; i++ {
res *= a
if res >= N {
res = 0
}
}
return res
}
func main() {
const N = 100001
var (
n, m, x int
count [N]int
)
fmt.Scan(&n, &m)
res := 0
for t := 0; t < n; t++ {
fmt.Scan(&x)
y := 1
z := 1
for i := 2; i*i <= x; i++ {
s := 0
if x % i == 0{
for {
if x%i != 0 {
break
}
x /= i
s += 1
}
}
s %= m
if s > 0 {
y *= power(i, s, N)
z *= power(i, m-s, N)
}
}
if x > 1 {
y *= x
z *= power(x, m-1, N)
}
// 因为y肯定是小于N的, 由于ai也是小于N的所以需要当大于z大于N之后就是不合法的将其置为0即可
if z >= N {
z = 0
}
res += count[z]
count[y] += 1
}
fmt.Println(res)
}
使用 bufio.NewReader() ,fmt.Fscan(),fmt.Fprintln() 函数优化输入输出数据,fmt.Fscan(r io.Reader) 从标准输入 r 中读取数据,fmt.Fprintln(w io.Writer) 将数据写入到标准输出 w 的缓存数据,最后在函数返回的时候需要将所有的缓存数据写入到标准输出中,运行效率比使用 fmt.Scan() 和 fmt.Println() 快 5~6 倍:
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func power(a int, b int, N int) int {
res := 1
for i := 0; i < b; i++ {
res *= a
if res >= N {
res = 0
}
}
return res
}
func run(r io.Reader, w io.Writer) {
in := bufio.NewReader(r)
out := bufio.NewWriter(w)
// 当函数返回的时候将数据写入到标准输出中
defer out.Flush()
const N = 100010
var (
n, m, x int
count [N]int
)
fmt.Fscan(in, &n, &m)
res := 0
for t := 0; t < n; t++ {
fmt.Fscan(in, &x)
y := 1
z := 1
for i := 2; i*i <= x; i++ {
s := 0
if x%i == 0 {
for {
if x%i != 0 {
break
}
x /= i
s += 1
}
}
s %= m
if s > 0 {
y *= power(i, s, N)
z *= power(i, m-s, N)
}
}
if x > 1 {
y *= x
z *= power(x, m-1, N)
}
if z >= N {
z = 0
}
res += count[z]
count[y] += 1
}
fmt.Fprintln(out, res)
}
func main() {
run(os.Stdin, os.Stdout)
}
python:
class Solution:
# 计算a ^ b
def power(self, a: int, b: int, N: int):
res = 1
while b > 0:
res *= a
# 当大于N之后都是没有意义的, 因为ai与aj都需要小于N
if res >= N: res = 0
b -= 1
return res
def process(self):
n, m = map(int, input().split())
a = list(map(int, input().split()))
N = 100001
count = [0] * N
res = 0
for x in a:
i = 2
# y, z对应aj, ai分解质因数之后不能够被k整除的i ^ ri' 与 i ^ ti'
y = z = 1
# 对于x分解质因数
while i * i <= x:
s = 0
while x % i == 0:
x //= i
s += 1
# 我们只关心s%m的余数
s %= m
if s:
y *= self.power(i, s, N)
z *= self.power(i, m - s, N)
i += 1
# 当前x也是一个质因数
if x > 1:
y *= x
z *= self.power(x, m - 1, N)
# aj一定小于N, 而要求匹配的ai也需要小于N所以当z大于等于N之后说明是不合法的
if z >= N: z = 0
# z中的次数就是当前与aj匹配的ai的数目
res += count[z]
# 将aj中不能够被k整除的质因子的次数对应的i^ri' 存储到哈希表中
count[y] += 1
return res
if __name__ == '__main__':
print(Solution().process())
线性筛求解最小质数:
1~10 ^ 5 范围内的质数个数为:x / Inx = 9592 个,所以还是比较多的,但是可以发现分解完质因数之后出现的质因子是很少的,10 ^ 5 之内的质因子个数还是很少的,2 * 3 * 5 * 7 * 11 * 13 * 17 = 510510,所以 10 ^ 5 之内的质因子最多有 6 个,这是一个很好的性质,我们可以使用线性筛求解出 10 ^ 5 以内的所有质数,在使用线性筛求解质数的过程可以使用一个数组或者列表 f 记录每一个数字的最小质数,这样我们就可以不用试除法分解质因数了,降低时间复杂度:
from typing import List
class Solution:
# 求解1~N范围的质数并且使用f记录某个数字的最小质数, f[x]表示x的最小质数
def getPrimes(self, n: int, f: List[int]):
st, primes = [0] * n, [0] * n
count = 0
for i in range(2, n):
if st[i] == 0:
primes[count] = i
count += 1
f[i] = i
j = 0
while primes[j] * i < n:
st[primes[j] * i] = 1
f[primes[j] * i] = primes[j]
if i % primes[j] == 0: break
j += 1
# 计算a ^ b
def power(self, a: int, b: int, N: int):
res = 1
for i in range(b):
res *= a
if res >= N: res = 0
return res
def process(self):
n, m = map(int, input().split())
a = list(map(int, input().split()))
N = 100001
f, count = [0] * N, [0] * N
self.getPrimes(N, f)
res = 0
for x in a:
y = z = 1
while x != 1:
s = 0
p = f[x]
while x % p == 0:
x //= p
s += 1
s %= m
if s > 0:
y *= self.power(p, s, N)
z *= self.power(p, m - s, N)
if z >= N:
z = 0
res += count[z]
count[y] += 1
return res
if __name__ == "__main__":
print(Solution().process())