#P2770. 第3题-元素相乘数组
-
2000ms
Tried: 94
Accepted: 6
Difficulty: 5
所属公司 :
米哈游
时间 :2025年3月29日
-
算法标签>哈希表
第3题-元素相乘数组
解题思路
1. 离散化:记录每个数字的下标
首先,我们需要记录数组中每个数字出现的最少两个下标。这个可以通过字典来实现。因为题目要求输出的下标是 1-indexed,我们会记录每个数字的最小两个下标。
2. 乘积的计算
对于每个查询的数 x,我们需要判断是否存在两个数组中的元素,它们的乘积等于 x。我们可以利用因式分解的方式来进行查找。具体步骤是:
- 对于一个查询数 x,遍历它的所有因子(即 x 的约数)。
- 对于每个因子 u,计算另一个因子 v=x/u。
- 如果数组中存在 u 和 v,则可以通过这两个元素的乘积得到 x。
3. 优化思路
由于题目中数据量非常大,直接暴力查找会超时。因此我们通过以下方法优化:
- 使用一个列表
ans来预处理所有可能的乘积结果。这样,对于每次查询,就可以直接通过查询该列表获得结果,避免重复计算。 - 在预处理时,遍历每个唯一元素及其倍数,记录每个倍数的最小两个下标。
4. 查询处理
查询时,只需要直接查找预处理结果即可,时间复杂度为常数 O(1)。
复杂度分析
-
时间复杂度:
- 对于预处理部分,遍历所有唯一元素及其倍数,时间复杂度大约是 O(nlogn),其中 n 是数组大小。由于我们对每个数的倍数进行处理,所以实际时间复杂度较低。
- 对于查询部分,查询操作为常数时间 O(1),因为我们通过预处理已经得到了所有可能的答案。
-
空间复杂度:
- 使用了一个长度为 106 的
ans列表来存储每个可能的乘积结果,因此空间复杂度为 O(106)。
- 使用了一个长度为 106 的
代码实现
Python 代码
n = int(input()) # 输入数组大小
a = list(map(int, input().split())) # 输入数组元素
# 离散化:获取数组中出现的唯一元素,并记录每个数字最少的两个下标(1-indexed)
unique_vals = sorted(set(a))
indices = {}
for i, x in enumerate(a, start=1):
if x not in indices:
indices[x] = [i]
else:
if len(indices[x]) < 2:
indices[x].append(i)
MAX_X = 1000000
# 注意:用列表生成式生成独立的子列表,避免使用 [[-1,-1]] * (MAX_X+1)(那样会产生浅拷贝)
ans = [[-1, -1] for _ in range(MAX_X + 1)]
# 利用调和级数思想预处理所有可能的乘积答案
# 对于每个唯一数字 u,在区间 [u, MAX_X] 内遍历所有 u 的倍数 m
# 设 m = u * k,则令 v = k。只有当 u >= v 且 v 在数组中存在时,才构成有效答案
for u in unique_vals:
for m in range(u, MAX_X + 1, u):
# 由于 m 为 u 的倍数,令 v = m // u
v = m // u
# 避免重复计算,只考虑 u >= v(若 u < v,则答案在之前已经计算)
if u < v:
break
if v in indices:
if u == v:
# u == v 时需要该数字至少出现两次
if len(indices[u]) >= 2:
i1, i2 = indices[u][0], indices[u][1]
ans[m] = [min(i1, i2), max(i1, i2)]
else:
i1 = indices[u][0]
i2 = indices[v][0]
ans[m] = [min(i1, i2), max(i1, i2)]
q = int(input()) # 输入查询次数
for _ in range(q):
x = int(input()) # 输入每次查询的x值
print(ans[x][0], ans[x][1])
Java 代码
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); // 输入数组大小
int[] a = new int[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
}
// 离散化:记录每个数字的下标
Map<Integer, List<Integer>> indices = new HashMap<>();
for (int i = 0; i < n; i++) {
int x = a[i];
if (!indices.containsKey(x)) {
indices.put(x, new ArrayList<>());
}
indices.get(x).add(i + 1); // 记录下标为1-indexed
}
int MAX_X = 1000000;
int[][] ans = new int[MAX_X + 1][2];
for (int i = 0; i <= MAX_X; i++) {
ans[i][0] = ans[i][1] = -1; // 初始化
}
// 预处理所有乘积结果
for (int u : indices.keySet()) {
for (int m = u; m <= MAX_X; m += u) {
int v = m / u;
if (u < v) break;
if (indices.containsKey(v)) {
if (u == v && indices.get(u).size() >= 2) {
List<Integer> idx = indices.get(u);
ans[m][0] = Math.min(idx.get(0), idx.get(1));
ans[m][1] = Math.max(idx.get(0), idx.get(1));
} else if (u != v) {
List<Integer> idx_u = indices.get(u);
List<Integer> idx_v = indices.get(v);
ans[m][0] = Math.min(idx_u.get(0), idx_v.get(0));
ans[m][1] = Math.max(idx_u.get(0), idx_v.get(0));
}
}
}
}
int q = sc.nextInt(); // 输入查询次数
for (int i = 0; i < q; i++) {
int x = sc.nextInt(); // 输入每次查询的x值
System.out.println(ans[x][0] + " " + ans[x][1]);
}
sc.close();
}
}
C++ 代码
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
unordered_map<int, vector<int>> indices;
for (int i = 0; i < n; i++) {
indices[a[i]].push_back(i + 1); // 记录下标为1-indexed
}
const int MAX_X = 1000000;
vector<vector<int>> ans(MAX_X + 1, vector<int>(2, -1));
// 预处理所有乘积结果
for (const auto& pair : indices) {
int u = pair.first;
for (int m = u; m <= MAX_X; m += u) {
int v = m / u;
if (u < v) break;
if (indices.find(v) != indices.end()) {
if (u == v && indices[u].size() >= 2) {
ans[m][0] = min(indices[u][0], indices[u][1]);
ans[m][1] = max(indices[u][0], indices[u][1]);
} else if (u != v) {
ans[m][0] = min(indices[u][0], indices[v][0]);
ans[m][1] = max(indices[u][0], indices[v][0]);
}
}
}
}
int q;
cin >> q;
while (q--) {
int x;
cin >> x;
cout << ans[x][0] << " " << ans[x][1] << endl;
}
return 0;
}
题目内容
米小游拿到了一个数组,她有若干次询问,每次询问输入一个x,她希望你判断x能否由数组中的两个元素相乘得出。
用数学语言描述,你需要寻找到两个下标i和j(i<j),满足ai∗aj=x
输入描述
第一行输入一个正整数n,代表数组的大小。
第二行输入n个正整数ai,代表数组的元素。
第三行输入一个正整数q,代表询问次数。
接下来的q行,每行输入一个正整数x,代表一次询问。
1≤n,q≤105
1≤ai,x≤106
输出描述
对于每次询问,如果无法找到两数乘积等于x,请输出−1−1。
否则输出和j(i<j),用空格隔开,代表ai∗aj=x。有多解时输出任意即可。
样例1
输入
5
1 2 3 2 4
2
4
5
输出
2 4
-1 -1
说明
第一组询问,输出15也是可以的。
第二组询问,显然无法找到两个元素相乘等于5。