cf 895C Square Subsets - 线性基

传送门

给出一个序列,找到一个子集,使得子集内的元素乘积是平方数。

考虑平方数的特点,就是唯一分解定理之后,质因子的幂一定是偶数。

数字很小,那么质因子肯定是在\([1,70]\)以内的,有\(18\)个。

那么就把每个数字用二进制表示,可以用18位的二进制表示,0表示偶数次,1表示奇数次

那相当于是集合里面的元素任何位的1的个数都是偶数才行。因为两个数字相乘,就是每一个质因子表示的位的异或。

那么我就需要找到一些集合,使得异或和是0就行了。

那么考虑到线性基的特点,线性基里的数是最少集合使得线性基里的数字异或和是非0。

假设线性基里的元素个数是\(s\),那么答案就是非线性基里真子集的个数,就是\(2^{n-s}-1\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MaxBasis = 20;///二进制位数
const int mod = 1e9 + 7;
template<typename T = long long> inline T read() {
    T s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
    while(isdigit(ch)) {s = (s << 3) + (s << 1) + ch - 48; ch = getchar();} 
    return s * f;
}
struct Linear_Basis {
    ll base[MaxBasis + 10]; bool rel; int sz;
    int tim[MaxBasis];
    vector<ll> Basis;/// 线性基(向量)
    Linear_Basis() { memset(base, 0, sizeof(base)); rel = false; sz = 0; Basis.clear();}
    void init() {
        memset(base, 0, sizeof(base)); rel = false; sz = 0; Basis.clear();
        memset(tim, 0, sizeof(tim));
    }
    bool add(ll x) { //加入线性基中
        for (int i = MaxBasis; i >= 0; --i) {
            if (!(x >> i & 1)) continue;
            if (base[i]) {x ^= base[i];}
            else {
                base[i] = x, ++sz;
                return 1;
            }
        }
        rel = true;
        return 0;
    }
    ll Max(ll ans = 0) { //取最大
        for(int i = MaxBasis; i >= 0; i--) 
            if(!(ans >> i & 1)) ans ^= base[i];
        return ans;
    }
    ll Min(ll ans = 0) { //取最小
        for(int i = 0; i <= MaxBasis; i++) ans = min(ans, ans ^ base[i]);
        return ans;
    }
    void GetBasis() { //构造向量,用于之后求第k小
        for (int i = 0; i <= MaxBasis; ++i)
            if (base[i]) Basis.push_back(base[i]);
    }
    void bin(struct Linear_Basis &b) { // 线性基求并(合并)
        for(int i = 0;i <= MaxBasis; i++) if(b.base[i]) add(b.base[i]);
    }
    Linear_Basis jiao(Linear_Basis a,Linear_Basis b){ // 线性基求交
        Linear_Basis g, tmp = a;
        ll cur, d;
        for(int i = 0;i <= MaxBasis;i++) if(b.base[i]){
            cur = 0,d = b.base[i];
            for(int j = i;j >= 0;j--) if(d>>j&1){
                if(tmp.base[j]){
                    d ^= tmp.base[j],cur ^= a.base[j];
                    if(d) continue;
                    g.base[i] = cur;
                }else tmp.base[j] = d, a.base[j] = cur;
                break;
            }
        }
        return g;
    }
    ll Min_Kth(ll k) { // 线性基中第k小
        if(rel) k--; // 线性基未满存在0
        if(k >= (1ll << sz)) return -1;
        ll ans = 0;
        for(int i = 0; i < sz; i++) if(k & (1ll << i)) ans ^= Basis[i];
        return ans;
    }
} lb;
const int N = 5e5 + 5;
ll a[N];
ll pow(ll a, ll b, ll p){
    ll ans = 1; a %= p;
    while(b){
        if(b & 1) ans = ans * a % p;
        a = a * a % p;
        b >>= 1;
    }
    return ans;
}
int pri[N], id[N], tot;
bool check(int n) {
    if(n == 1) return 0;
    for(int i = 2; i * i <= n; i++) if(n % i == 0) return 0;
    return 1;
}
int main(){
    int n = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    for(int i = 1; i <= 70; i++) if(check(i)) pri[++tot] = i, id[i] = tot;
    for(int i = 1; i <= n; i++) {
        int x = a[i]; a[i] = 0;
        for(int j = 2; j * j <= x; j++) {
            if(x % j == 0) {
                int num = 0;
                while(x % j == 0) x /= j, num++;
                if(num & 1) a[i] |= 1 << id[j];
            }
        }
        if(x > 1) a[i] |= 1 << id[x];
        // cout << a[i] << endl;
    }
    for(int i = 1; i <= n; i++) lb.add(a[i]);
    printf("%lld\n", (pow(2, n - lb.sz, mod) - 1 + mod) % mod);
    return 0;
}
posted @ 2021-02-14 17:34  Emcikem  阅读(58)  评论(0编辑  收藏  举报