hdu5833 Zhu and 772002

题目大意:从n个数a1,a2,…,an里选出若干个数(至少选一个),然后把选出的数乘起来得到b,问b是完全平方数的取数方法有多少种。每个数的质因子不超过2000,答案模上1000000007。
这道题目和POJ1830开关问题类似。首先,n个数中每个数都有取与不取两种操作,令向量 x⃗ T=(x1,x2,,xn) ,其中 xi 表示是否取第 i 个数,0表示不取, 1 表示取。现在我们要判断操作是否合法(即取出的数的积为完全平方数)。不难发现,完全平方数的相同质因子的指数总是偶数,判断若干个数的积是否为完全平方数就看那些数对应的相同质因子的指数的和是否为偶数。若令fij表示第 j 个数的第i个质因子的指数,则 nj=1fijxj 表示取出的数的第 i 个质因子的指数之和(或者说取出的数的乘积的第i个质因子的指数),显然,当所有的质因子的指数均为偶数时,取出的数的的积为完全平方数,即方程组 nj=1fijxjmod2=0 ,题目问的是可行方案数,也就是解的个数,由于是否取模只影响解的值,不影响解的个数,问题转换为求齐次线性方程组 Fx⃗ =0 的解的个数。由于仅 01 两种取值,解的个数等于 2r ,其中r表示解向量中自由变元的个数。题目中说至少取一个数,故零解不合法,最终答案为 2r1
题目中说质因子的大小不会超过2000,分解质因子可行,对每个数分解质因子即可得到矩阵 F 。由于我们只关心质因子指数的奇偶性,可将每个元素都模2(或者说将原始方程组里的mod2放到 fij 后面),将其变成01矩阵,提高数值稳定性。实际上,由于“模2加”等价于异或,程序中均用异或来代替。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef unsigned long long ull;
const int maxn = 2001, mod = 1000000007;

bool isnpri[maxn];
int prime[maxn], k;
inline void getPrime()
{
    for (int i = 2; i < maxn; i++)
    {
        if (!isnpri[i]) prime[k++] = i;
        for (int j = 0; j < k && i*prime[j] < maxn; j++)
        {
            isnpri[i*prime[j]] = true;
            if (i%prime[j] == 0)    break;
        }
    }
}

ull a[300];
int fac[maxn][300];
inline void factor(int n)
{
    ull now = a[n];
    for (int i = 0; i < k; i++)
        while (now%prime[i] == 0)
            fac[i][n] ^= 1, now /= prime[i];
}

inline int gauss(int n)
{
    int i = 0, j = 0;
    while (j < n)
    {
        int id = i;
        for (int k = i; k < ::k; k++)
            if (abs(fac[k][j])) id = k;
        if (id != i)
        {
            for (int k = j; k < n; k++)
                swap(fac[i][k], fac[id][k]);
        }
        if (fac[i][j] == 0) { j++; continue; }
        for (int k = i + 1; k < ::k; k++)
        {
            if (fac[k][j] == 0) continue;
            for (int l = j; l < n; l++)
                fac[k][l] ^= fac[i][l];
        }
        i++, j++;
    }
    return n - i;
}

inline ull pow_mod(ull x, ull n)
{
    ull ret = 1;
    while (n)
    {
        if (n & 1)
            ret = ret*x%mod;
        x = x*x%mod;
        n >>= 1;
    }
    return ret;
}

int main()
{
    getPrime();  //筛出2000内的素数
    int T, t = 0;
    cin >> T;
    while (T--)
    {
        memset(fac, 0, sizeof(fac));
        int n;
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            cin >> a[i];
            factor(i);    //分解质因数
        }
        int free_element = gauss(n);
        ull ans = pow_mod(2, free_element) - 1;
        cout << "Case #" << ++t << ":\n";
        cout << ans << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值