UVa 11191 Square

题目描述

Byteland\texttt{Byteland}Byteland 的居民不喜欢大质数,因此他们从不使用含有大于 303030 的质因数的整数。他们喜欢完全平方数。如果一个整数的平方根是整数,则该整数是完全平方数。0,1,4,90, 1, 4, 90,1,4,9 是完全平方数,但 −4-44333 不是。

现在 Byteland\texttt{Byteland}Byteland 的居民有一个包含 nnn 个数字的序列。他们从中选择 C2C_2C2 对数字。如果一对数字的乘积是完全平方数,则该对是平方对。他们想要计算这些 C2C_2C2 对中有多少对是平方对(记为 XXX)。

同样,他们从序列中选择 C3C_3C3 个三元组。如果一个三元组的乘积是完全平方数,则该三元组是平方三元组。他们想要计算这些 C3C_3C3 个三元组中有多少个是平方三元组(记为 YYY)。请帮助他们计算 XXXYYY

输入格式

第一行包含 TTT,表示测试用例的数量。接下来是 TTT 个测试用例。

每个测试用例以一行整数 nnn 开始,表示序列的长度。下一行包含 nnn 个用空格分隔的整数。

输出格式

对于每个测试用例,输出两个整数 XXXYYY,用空格分隔。

约束条件

  • 0<n≤2000000 < n \leq 2000000<n200000
  • 序列中每个数字的绝对值 <1018< 10^{18}<1018
  • 除了数字 000 作为例外,序列中没有数字的质因数大于 303030

题目分析

关键观察

  1. 完全平方数的性质:一个数是完全平方数当且仅当它的所有质因数的指数都是偶数。

  2. 乘积的完全平方性:两个数的乘积是完全平方数当且仅当每个质因数的总指数是偶数。对于三个数也是类似的道理。

  3. 符号处理:由于完全平方数必须是非负数,因此乘积的符号也必须是非负的(即负数的个数必须是偶数)。

  4. 数字 000 的特殊性000 与任何数的乘积都是 000,而 000 是完全平方数。

解题思路

核心思想:数字签名

我们可以为每个非零数字定义一个签名signature\texttt{signature}signature),该签名编码了两个信息:

  • 质因数指数的奇偶性
  • 数字的符号

具体来说:

  • 签名的第 111101010 位表示 101010 个小质数 (2,3,5,7,11,13,17,19,23,29)(2, 3, 5, 7, 11, 13, 17, 19, 23, 29)(2,3,5,7,11,13,17,19,23,29) 的指数奇偶性
  • 签名的第 000 位(最低位)表示数字的符号(000 表示正,111 表示负)
签名计算方法

对于每个非零数字 xxx

  1. 取绝对值 ∣x∣|x|x
  2. 对于每个质数 ppp,统计 ∣x∣|x|x 中包含 ppp 的指数,如果指数是奇数,则在签名的对应位置设置 111
  3. 将签名左移 111 位,为符号位留出空间
  4. 如果 x<0x < 0x<0,则在最低位设置 111
平方对的判断

两个数字的乘积是完全平方数当且仅当:

  • 它们的签名相同(质因数部分相同且符号满足乘积为正)
平方三元组的判断

三个数字的乘积是完全平方数当且仅当:

  • 它们的签名异或结果为 000(这自动保证了质因数部分异或为 000 且符号位异或为 000,即负数的个数为偶数)

算法步骤

  1. 统计频率

    • 统计数字 000 的个数
    • 对于每个非零数字,计算其签名并统计各签名的出现频率
  2. 计算平方对 XXX

    • 相同签名的数字可以两两配对:count×(count−1)2\frac{count \times (count-1)}{2}2count×(count1)
    • 包含 000 的配对:000 与任何数字配对都是平方对
  3. 计算平方三元组 YYY

    • 包含 000 的三元组:直接计算组合数
    • 三个非零数字的三元组:寻找三个签名异或为 000 的组合

复杂度分析

  • 时间复杂度O(T×(n+k3))O(T \times (n + k^3))O(T×(n+k3)),其中 kkk 是不同签名的数量。由于签名空间有限,kkk 不会太大。
  • 空间复杂度O(n)O(n)O(n),用于存储频率统计。

代码实现

// Square
// UVa ID: 11191
// Verdict: Accepted
// Submission Date: 2025-11-28
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

#include <bits/stdc++.h>
using namespace std;

const int primes[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};

int getSignature(long long n) {
    int signature = 0;
    long long nn = abs(n);
    for (int i = 0; i < 10; i++) {
        int cnt = 0;
        while (nn % primes[i] == 0) { cnt++; nn /= primes[i]; }
        if (cnt % 2) signature |= (1 << i);
    }
    signature <<= 1;
    if (n < 0) signature |= 1;
    return signature;
}

int main() {
    cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while (T--) {
        int n;
        cin >> n;
        vector<long long> ns(n);
        for (int i = 0; i < n; i++) cin >> ns[i];
        map<int, long long> signatureCount;
        int zeroCount = 0;
        for (int i = 0; i < n; i++) {
            if (ns[i] == 0) zeroCount++;
            else {
                int sig = getSignature(ns[i]);
                signatureCount[sig]++;
            }
        }
        long long squarePairs = 0;
        for (auto& entry : signatureCount) squarePairs += entry.second * (entry.second - 1) / 2;
        squarePairs += 1LL * zeroCount * (zeroCount - 1) / 2;
        squarePairs += 1LL * zeroCount * (n - zeroCount);
        long long squareTriples = 0;
        long long nonZeroCount = n - zeroCount;
        squareTriples += 1LL * zeroCount * (zeroCount - 1) * (zeroCount - 2) / 6;
        squareTriples += 1LL * zeroCount * (zeroCount - 1) * nonZeroCount / 2;
        squareTriples += 1LL * zeroCount * nonZeroCount * (nonZeroCount - 1) / 2;
        for (auto& entry1 : signatureCount) {
            int sig1 = entry1.first;
            long long count1 = entry1.second;
            for (auto& entry2 : signatureCount) {
                if (entry2.first < sig1) continue;
                int sig2 = entry2.first;
                long long count2 = entry2.second;
                int sig3 = sig1 ^ sig2;
                if (sig3 < sig2) continue;
                if (!signatureCount.count(sig3)) continue;
                long long count3 = signatureCount[sig3];
                if (sig1 == sig2 && sig2 == sig3) squareTriples += count1 * (count1 - 1) * (count1 - 2) / 6;
                else if (sig2 == sig3) squareTriples += count1 * count2 * (count2 - 1) / 2;
                else squareTriples += count1 * count2 * count3;
            }
        }
        cout << squarePairs << " " << squareTriples << "\n";
    }
    return 0;
}

总结

本题的关键在于将数学问题转化为位运算问题,通过数字签名来统一处理质因数奇偶性和符号信息。这种方法不仅代码简洁,而且能够系统地处理所有可能的情况,避免了复杂的分类讨论和边界情况处理。

签名异或为 000 的条件同时保证了质因数指数的偶数性和乘积符号的正确性,这是本解法最巧妙的地方。对于包含 000 的情况,由于 000 的特殊性质,我们可以直接计算组合数,进一步简化了问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值