[lnsyoj895C/luoguCF895C] Square Subsets

题意

给出一个序列 \(a\),求满足乘积是完全平方数的非空子集个数和。

sol

由于完全平方数一定需要满足质因数的个数均为偶数,所以可以设 \(state\) 表示某个数的质因数个数是奇数还是偶数,合并时用异或即可。
考虑一种稍暴力的写法:设 \(f_{i,state}\) 表示到 \(a_i\) 时,子集乘积状态为 \(state\) 的方案个数,设 \(s_i\) 表示数 \(a_i\) 对应的状态。
容易得出 \(f_{i,state}=f_{i-1,state\oplus s_i} + f_{i-1,state}\),其中前者为选择该数,后者为不选择该数。
状态数不可接受。
考虑到值域为 \(70\),因此有很多重复的数,而这些数的顺序并无关联,因此考虑变更状态为 \(f_{i,state}\) 表示到数 \(i\) 时,子集乘积状态为 \(state\) 的方案个数。
假设对于某个数,数列中有 \(k\) 个,那么选择偶数个这个数的方案数为

\[\sum_{i=0, i\equiv\pmod2}^k C_k^i = \sum_{i=0}^{\lfloor \frac{k}{2} \rfloor} C_k^{2i}=\sum_{i=0}^{\lfloor \frac{k}{2} \rfloor}C_{k-1}^{2i-1}+C_{k-1}^{2i}=\sum_{i=0}^{k-1}C_{k-1}^i=2^{k-1} \]

奇数同理为 \(2^{k-1}\),因此 \(f_{i,state}=f_{i-1,state\oplus s_i} \cdot 2^{k-1} + f_{i-1,state} \cdot 2^{k-1}\),其中前者为选择奇数个,后者为选择偶数个。

代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;

const int N = 75, M = 25, K = 1 << 19, S = 100005, mod = 1e9 + 7;

int cnt[N], state[N];
int primes[M] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67};
int n;
int pow2[S];
int f[N][K];

int main(){
    scanf("%d", &n);
    pow2[0] = 1;
    for (int i = 1; i <= n; i ++ ) {
        int t;
        scanf("%d", &t);
        cnt[t] ++ ;
        pow2[i] = (LL) pow2[i - 1] * 2 % mod;
    }

    for (int i = 1, k = 1; i <= 70; i ++ , k = i) 
        for (int j = 0; j < 19; j ++ )
            if (k % primes[j] == 0)
                while (k % primes[j] == 0)
                    state[i] ^= 1 << j, k /= primes[j];
    
    f[0][0] = 1;
    int lst = 0;
    for (int i = 1; i <= 70; i ++ ) {
        if (!cnt[i]) continue;
        for (int s = 0; s < K; s ++ ) {
            int ns = s ^ state[i];
            f[i][s] = (LL) f[lst][s] * pow2[cnt[i] - 1] % mod;
            f[i][s] = (f[i][s] + (LL) f[lst][ns] * pow2[cnt[i] - 1] % mod) % mod;
        }
        lst = i;
    }

    printf("%d\n", (f[lst][0] - 1 + mod) % mod);
}
posted @ 2025-02-14 19:40  是一只小蒟蒻呀  阅读(12)  评论(0)    收藏  举报