G2. Magic Triples (Hard Version)
G2. Magic Triples (Hard Version)
This is the hard version of the problem. The only difference is that in this version, .
For a given sequence of integers , a triple is called magic if:
- .
- , , are pairwise distinct.
- there exists a positive integer such that and .
Kolya received a sequence of integers as a gift and now wants to count the number of magic triples for it. Help him with this task!
Note that there are no constraints on the order of integers , and .
Input
The first line contains a single integer () — the number of test cases. The description of the test cases follows.
The first line of the test case contains a single integer () — the length of the sequence.
The second line of the test contains integers () — the elements of the sequence .
The sum of over all test cases does not exceed .
Output
For each test case, output a single integer — the number of magic triples for the sequence .
Example
input
7 5 1 7 7 2 7 3 6 2 18 9 1 2 3 4 5 6 7 8 9 4 1000 993 986 179 7 1 10 100 1000 10000 100000 1000000 8 1 1 2 2 4 4 8 8 9 1 1 1 2 2 2 4 4 4
output
6 1 3 0 9 16 45
Note
In the first example, there are magic triples for the sequence — , , , , , .
In the second example, there is a single magic triple for the sequence — .
解题思路
先给出G1. Magic Triples (Easy Version)的做法。
暴力的做法还是很容易想到的,我们枚举三元组中的,然后从开始枚举,看一下数组中是否存在和。因此还需要先开个哈希表统计数组中每个元素出现的次数,记作,表示在数组中出现了次。
如果,那么有,因此如果,那么根据乘法原理,值均为的三元组数量就是。因此可以根据元素的不同种类来分别计算答案。为了方便,对于的情况,我们可以枚举每一个元素,然后求,得到的结果与前一种方法是一样的,注意到同一类元素的数量为,就是把分解成若干个累加而已。
如果,我们要保证,这里的。即,因此最大枚举到。然后根据乘法原理,满足条件的三元组数量就是。
然后比较坑的地方是由于是多组测试数据,因此每次都 memset 一个值域数组肯定会超时的,而用 std::unordered_map 肯定会被卡,用 std::map 时间复杂度就达到也有可能会超时,就很难办了。比赛的时候在这个地方卡了很久,最后还是交了 std::unordered_map 的做法,当然也不出意外的fst了。
其实要用数组实现哈希表也非常的简单,一般情况下都是直接对整个数组清零,实际上在一组数据中有很多地方都没有用到,如果直接全部清零很明显浪费了很多的时间。做法是在跑完一组数据后,只对用过的地方清零就好了。在这题中做法就是令一组数据中所有的。
AC代码如下,时间复杂度为:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 2e5 + 10, M = 1e6 + 10; 7 8 int n; 9 int a[N]; 10 int cnt[M]; 11 12 void reset() { 13 for (int i = 0; i < n; i++) { 14 cnt[a[i]] = 0; 15 } 16 } 17 18 void solve() { 19 scanf("%d", &n); 20 for (int i = 0; i < n; i++) { 21 scanf("%d", a + i); 22 cnt[a[i]]++; 23 } 24 LL ret = 0; 25 for (int i = 0; i < n; i++) { 26 if (cnt[a[i]] >= 3) ret += (cnt[a[i]] - 1ll) * (cnt[a[i]] - 2); 27 } 28 for (int i = 0; i < n; i++) { 29 int t = 1000000 / a[i]; 30 for (int j = 2; j <= t / j; j++) { 31 ret += 1ll * cnt[a[i] * j] * cnt[a[i] * j * j]; 32 } 33 } 34 printf("%lld\n", ret); 35 reset(); 36 } 37 38 int main() { 39 int t; 40 scanf("%d", &t); 41 while (t--) { 42 solve(); 43 } 44 45 return 0; 46 }
然后就是Hard版本了,扩大到了,很明显上面的做法已经不适用了。上面的做法是枚举三元组中的,这里的做法是枚举中间的元素(想不到就真的做不出来了)。然后更妙的是还要把分成两种情况,即和。
首先对于对于的情况做法与上面的一样,下面来讨论的情况。
如果,那么很明显对于,应该有,即,因此最大枚举到。最后如果满足,那么满足条件的三元组数量就是。
如果,因为有,因此必然是的一个约数,意味着我们可以枚举出的所有约数,如果满足,那么满足条件的三元组数量就是。其中分解约数的时间复杂度为。
因此整个做法的时间复杂度就是。
再补充一下debug记录。这里我是直接手写哈希表来实现,STL是真不敢用了。然后呢我把哈希表开到了的大小,结果T麻了,我百思不得其解。然后我试着把哈希表大小开到就过了。这是因为表明上看起来只用映射的数据量,但实际上还有和这些数据,因为还是需要在哈希表中查找的,如果哈希表比较小那么哈希冲突就会很明显。如果遇到数据,那么在哈希查找的过程中就会TLE。因此可以尝试把哈希表大小多开几倍,但也不能开太大,一方面是有空间限制,另一方面是如果大小过大,那么在内存中申请空间所需要的时间也会变大,也是有可能会TLE的。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 6 const int N = 2e5 + 10, M = 1e7 + 19; 7 8 int n; 9 int a[N]; 10 int h[M], cnt[M]; 11 12 int find(int x) { 13 int k = x % M; 14 while (h[k] && h[k] != x) { 15 if (++k == M) k = 0; 16 } 17 return k; 18 } 19 20 void reset() { // 只把用过的位置清零 21 for (int i = 0; i < n; i++) { 22 a[i] = find(a[i]); 23 } 24 for (int i = 0; i < n; i++) { 25 h[a[i]] = cnt[a[i]] = 0; 26 } 27 } 28 29 void solve() { 30 scanf("%d", &n); 31 for (int i = 0; i < n; i++) { 32 scanf("%d", a + i); 33 int t = find(a[i]); // 把a[i]映射到t 34 h[t] = a[i]; 35 cnt[t]++; 36 } 37 LL ret = 0; 38 for (int i = 0; i < n; i++) { // b=1的情况 39 int t = find(a[i]); 40 if (cnt[t] >= 3) ret += (cnt[t] - 1ll) * (cnt[t] - 2); 41 } 42 for (int i = 0; i < n; i++) { // b>=2的情况 43 if (a[i] >= 1000000) { // a[j]>=M^{2/3}的情况,暴力枚举b 44 int t = 1000000000ll / a[i]; 45 for (int j = 2; j <= t; j++) { // b最大枚举到M^{1/3} 46 if (a[i] % j == 0) { // a[j] mod b 要等于0,这样才有a[i] 47 int t1 = find(a[i] / j), t2 = find(a[i] * j); 48 if (h[t1] && h[t2]) ret += 1ll * cnt[t1] * cnt[t2]; 49 } 50 } 51 } 52 else { 53 for (int j = 1; j <= a[i] / j; j++) { // a[j]<M^{2/3}的情况,分解约数 54 if (a[i] % j == 0) { 55 if (j > 1 && a[i] <= 1000000000ll / j) { // 约数不能为1,且a[k]没有超过M 56 int t1 = find(a[i] / j), t2 = find(a[i] * j); 57 if (h[t1] && h[t2]) ret += 1ll * cnt[t1] * cnt[t2]; 58 } 59 if (a[i] / j != j && a[i] <= 1000000000ll / a[i] * j) { 60 int t1 = find(j), t2 = find(a[i] / j * a[i]); 61 if (h[t1] && h[t2]) ret += 1ll * cnt[t1] * cnt[t2]; 62 } 63 } 64 } 65 } 66 } 67 printf("%lld\n", ret); 68 reset(); 69 } 70 71 int main() { 72 int t; 73 scanf("%d", &t); 74 while (t--) { 75 solve(); 76 } 77 78 return 0; 79 }
参考资料
Codeforces Round #867 (Div. 3) Editorial:https://codeforces.com/blog/entry/115409
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17356304.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效