1 条题解

  • 0
    @ 2025-8-24 23:00:39

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Tomle
    bzd

    搬运于2025-08-24 23:00:39,当前版本为作者最后更新于2024-07-19 11:07:53,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    前言

    一个月没写题解了,为了挽救估值,精心写了这篇题解。

    本文有些冗长,可能会浪费您的时间,但可以对这道题有更清晰的理解。

    约定

    1. 下文中 \bigoplus 表示异或
    2. 质数 ppaa 中出现了 xx 次表示将 aa 分解质因数后,pp 一共出现了 xx 次。

    思路

    二进制表示

    首先,我们把每个数 aia_i 分解质因数,显然,aia_i 为完全平方数当且仅当每个质数都出现了偶数次。

    注意到本题 1ai301\le a_i\le30,其中的质数只有 2,3,5,7,11,13,17,19,23,292,3,5,7,11,13,17,19,23,29,共 1010 个。我们不妨将 1301\sim30 中每个数用二进制表示:对于第 ii 个质数(第 ii 位),若它出现了奇数次,这个二进制数的第 ii 位为 11,若出现了偶数次,则为 00

    两数相乘的二进制表示

    由于本题需要乘法,我们考虑当知道 aabb 的二进制表示 xxyy,如何求出 abab 的二进制表示 zz。对于第 ii 个质数 pip_i,共有四种情况:

    • pip_iaa 中出现了偶数次,xx 的第 ii 位为 00pip_ibb 中出现了偶数次,yy 的第 ii 位为 00,则 pip_iabab 中出现了偶数次,zz 的第 ii 位为 00

    • pip_iaa 中出现了偶数次,xx 的第 ii 位为 00pip_ibb 中出现了奇数次,yy 的第 ii 位为 11,则 pip_iabab 中出现了奇数次,zz 的第 ii 位为 11

    • pip_iaa 中出现了奇数次,xx 的第 ii 位为 11pip_ibb 中出现了偶数次,yy 的第 ii 位为 00,则 pip_iabab 中出现了奇数次,zz 的第 ii 位为 11

    • pip_iaa 中出现了奇数次,xx 的第 ii 位为 11pip_ibb 中出现了奇数次,yy 的第 ii 位为 11,则 pip_iabab 中出现了偶数次,zz 的第 ii 位为 00

    容易发现,以上每种情况其实都是在进行异或操作,推出 z=xyz=x\bigoplus y

    本题主要部分

    对于本题,令 bib_iii 的二进制表示,我们要维护一个前缀异或和,也就是 prei=j=1ibaipre_i=\bigoplus_{j=1}^ib_{a_i}prei=prei1baipre_i=pre_{i-1}\bigoplus b_{a_i},特别的,pre0=0pre_0=0

    容易发现,区间 [l,r][l,r] 满足答案当且仅当 prer=prel1pre_r=pre_{l-1},当 prerpre_rprel1pre_{l-1}11 的位异或后相互抵消时,[l,r][l,r] 中数的乘积才是完全平方数。

    cnticnt_i 表示二进制表示为 ii 的数的个数。我们从 1n1\sim n 扫描每个数 aia_i,右端点为 ii 满足条件的区间的个数为 cntpreicnt_{pre_i},将其计入答案后更新 cntpreicnt_{pre_i}

    细节

    1. cntcnt 数组大小开到 2102^{10}
    2. 先计入答案,后更新(ans += cnt[prexor]++;)。
    3. pre0=0pre_0=0,初始时 cnt0=1cnt_0=1
    4. 十年 OI 一场空,不开 【】 见祖宗!

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    int n, a[100005], b[35], prexor;
    long long ans, cnt[1 << 10];
    vector <int> prime = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
    
    void read(int &a, int ch = 0) {
    	while (!isdigit(ch = getchar()));
    	for (a = 0; isdigit(ch); ch = getchar()) a = (a << 3) + (a << 1) + (ch ^ 48);
    }
    void prework() {
    	// 预处理数组 b
    	for (int i = 1; i <= 30; i++) {
    		int j = i;
    		for (int k = 0; k < 10; k++) {
    			while (j % prime[k] == 0) {
    				b[i] ^= 1 << k;
    				j /= prime[k];
    			}
    		}
    	}
    }
    int main() {
    	prework();
    	read(n);
    	cnt[0] = 1;
    	for (int i = 1; i <= n; i++) {
    		read(a[i]);
    		prexor ^= b[a[i]];
    		ans += cnt[prexor]++;
    	}
    	cout << ans;
    	return 0;
    }
    

    写在最后

    本题思维难度和代码难度都一般,感觉评绿有点高了,应该是上位黄。

    本人表达能力较差,简单的思路被写得很冗长,但将其底层逻辑刨析得较为透彻。

    本人写文章偏向学术,有些内容可能晦涩难懂,欢迎在评论区指出。

    • 1

    信息

    ID
    10503
    时间
    1000ms
    内存
    500MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者