题解:P10724 [GESP202406 七级] 区间乘积

· · 题解

题目传送门

题目大意

给出 n 个数 a_i,求有多少对 \lang l,r \rang,满足 1 \le l \le r \le na_l \times a_{l+1} \times \dots \times a_{r-1} \times a_r 为完全平方数。

一个正整数 x 为完全平方数,当且仅当存在一个正整数 y,使得 x = y^2

思路讲解

阶段一:前缀积

首先,我们将问题进行一个类似前缀和的转化。

p_i\prod_{j=1}^{i} a_j,即序列 Ai 个数的乘积,则题目可转换为存在多少对 \lang l,r \rang,满足 \frac{p_r}{p_{l-1}}(p_0 = 1) 为完全平方数。

阶段二:分解质因数

可是 p_i 最高可达到 30^{10^5},存不下。需要再次优化。

学过奥数的朋友应该都知道,完全平方数的因数个数为奇数,反之亦然,因为它的平方根只能算一次。

而求出一个数因数个数,只需将它分解质因数后将所有质因数的指数先各自加 1 后相乘即可。因为它的因数都可以表述为它的若干个质因数的乘积(1 除外),所以每个质因数可以取走 0 到该质因数指数个(取走 0 个则作为 1 相乘,如果都不取则结果为 1)。

再回到题目,可以发现 a_i 的上限很小,只有 30,所以我们可以列出 30 以内的所有质数 2,3,5,7,11,13,17,19,23,29,并定义一个二维数组 s_{i,j},表示序列 a 的前 i 个数的乘积分解质因数后第 j 个质数的指数为多少(如果为 0 则表示不存在)。

在给 s_{i,j} 赋值时,就可以统计 a_i 分解质因数后,第 j 个质数的指数(不存在为 0),再加上 s_{i-1,j} 的指数。在 p_{l-1}p_r 进行相除时,只需将 s_r 的每一个 s_{r,i} 减去 s_{l-1} 的每一个 s_{l-1,j} 即可(1 \le i,j \le 10)。

判断是否为完全平方数时只需用上前面说的方法,将减后的指数加 1 并相乘,求出因数个数。如果因数为奇数个,则该数是完全平方数,否则不是。

这样我们就可以枚举所有的 \langle l,r \rangle,查看有多少个的乘积为完全平方数。算法复杂度为 O(n^2)

阶段三:状态压缩+异或

如果要使所有质因数的指数加 $1$ 后的乘积为奇数,那乘积就不能含有质因子 $2$,所以所有质因数加 $1$ 后的结果必需为奇数,也就是说所有质因子的指数必需为偶数,所以我们只关心 $p_{r}$ 除以 $p_{l-1}$ 后所有的质因数的指数是否为偶数。 所以我们可以将 $s_{i,j}$ 压缩成 $s_i$,$s_i$ 从右往左数第 $j$ 个二进制位表示 $p_i$ 第 $j$ 个质数的指数模 $2$ 的余数(如果 $p_i$ 的质因数中不存在第 $j$ 个质数,则指数算作 $0$)。 可是 $s_i$ 如果直接相加可能会进位,该如何避免进位呢? 按位异或相信大家都不陌生,在编程时可以用 `^` 来表示。它的运算规则为遍历每一个**二进制位**,如果不同则答案的这一位为 $1$,否则为 $0$。 不过,我们可以换一种方式来表示它的运算规则——相加但不进位。因为不管是按照依照原来的规则,还是新定义出来的规则,都不影响结果。 所以我们在算出 $a_i$ 的质因数后,就可让 $s_i$ 直接异或 $s_{i-1}$。 在 $p_r$ 和 $p_{l-1}$ 进行相除时,就可以直接将 $s_r$ 的每一位减去 $s_{l-1}$ 的每一位(如果不足则自己加上 $2$),如果每一位为 $0$,则说明 $s_r$ 和 $s_l$ 的质因子相减后每一位为偶数,即 $p_r \div p_{l-1}$ 为完全平方数,也就是说 $\lang l,r \rang$ 满足题目要求。 仔细观察,不难发现,如果$s_r$ 的每一位减去 $s_{l-1}$ 的每一位(如果不足则自己加上 $2$)后,每一位为 $0$,则说明 $s_r$ 和 $s_{l-1}$ 相等。 所以我们可以遍历右端点,并将答案加上前面所有和 $s_r$ 相等的 $s_{l-1}(l \le i)$ 的数量即可,统计 $s_j$ 的操作可以通过 `map` 实现。 注意,$l=1$ 时 $s_{l-1}$ 为 $0$,要提前加入 `map` 中。 算法复杂度为 $O(n \log{n})$。 # 代码实现 ```cpp #include <bits/stdc++.h> using namespace std; int prime[11]={0,2,3,5,7,11,13,17,19,23,29}; //统计小于30的质数 map<long long,long long> cnt; long long n,a[100005],s[100005],ans; int main(){ cin>>n; cnt[0]=1; for(int i=1;i<=n;i++){ cin>>a[i]; int tmp=0; for(int j=1;j<=10;j++){ //统计s[i] int sum=0; while(a[i]%prime[j]==0) sum++,a[i]/=prime[j]; tmp+=sum%2,tmp*=2;//赋值后左移一位 } s[i]=s[i-1]^tmp; ans+=cnt[s[i]]++; //统计前面出现多少次s[i],并将s[i]出现的次数加1 } cout<<ans; return 0; } ``` 完结撒花!