Codeforces 895.C Square Subsets

本文介绍了一种算法,用于解决给定数组中选取若干个数使得其乘积为完全平方数的问题。通过状态压缩的方法,记录每个质因数的奇偶性,并结合组合数学知识,最终求得所有可能的方案数。
C. Square Subsets
time limit per test
4 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Petya was late for the lesson too. The teacher gave him an additional task. For some array a Petya should find the number of different ways to select non-empty subset of elements from it in such a way that their product is equal to a square of some integer.

Two ways are considered different if sets of indexes of elements chosen by these ways are different.

Since the answer can be very large, you should find the answer modulo 109 + 7.

Input

First line contains one integer n (1 ≤ n ≤ 105) — the number of elements in the array.

Second line contains n integers ai (1 ≤ ai ≤ 70) — the elements of the array.

Output

Print one integer — the number of different ways to choose some elements so that their product is a square of a certain integer modulo 109 + 7.

Examples
input
4
1 1 1 1
output
15
input
4
2 2 2 2
output
7
input
5
1 2 4 5 8
output
7
Note

In first sample product of elements chosen by any way is 1 and 1 = 12. So the answer is 2^4 - 1 = 15.

In second sample there are six different ways to choose elements so that their product is 4, and only one way so that their product is 16. So the answer is 6 + 1 = 7.

大致题意:选若干个数使得乘积为完全平方数,问方案数.

 分析:对于完全平方数,它的每个质因子的次数都是偶数.这道题中ai ≤ 70,质因子最多只有19个,可以考虑状压,记一个状态sta,第i位表示第i个质因子的次数为偶数还是奇数,状态的变化可以通过位运算中的异或运算来解决.那么可以设计状态f[i][j]表示前i个数字中状态为j的方案数,每个数字有选或不选两种选择,转移也比较明确.比较遗憾的是n比较大,2^19 * n不足以通过此题.需要换一种状态的表达方式.

           首先状态j,也就是第二维是不能去掉的,关键是第一维该换成什么.2^19已经比较大了,那么第一维也不能很大,能想到的就是表示成1到大小为i的数字中,状态为j的方案数:f[i][j],这样的话不一位一位地去考虑,而是去考虑每个数字出现的次数.转移的时候枚举i和上一次的状态j,那么j能够转移到状态j ^ sta[i]和j,转移到j ^ sta[i]就需要取奇数个i,方案数就是C(n,1) + C(n,3) + ...... = 2^(n-1).j转移到j取偶数个就行了,方案数还是2^(n-1).

           最后的答案就是f[70][0].

           一道非常好的题.设计状态保留必须的,替换其它的,从题目的要求和信息中找到可以替换的状态.也可以从另一个思路来理解这个替换:不需要知道i在哪几位被选,只需要知道i被选了多少次,记录出现的次数并利用组合数学的知识就可以解决了.

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

using namespace std;

typedef long long ll;

const int mod = 1e9 + 7;

ll n, a[100010], cnt[80], jie[100010];
int prime[19] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67 };
int f[71][(1 << 19) + 5], stu[80];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        cnt[a[i]]++;
    }
    jie[0] = 1;
    for (int i = 1; i <= n; i++)
        jie[i] = (jie[i - 1] * 2) % mod;
    for (int i = 1; i <= 70; i++)
    {
        for (int j = 0; j < 19; j++)
        {
            int x = i;
            while (x % prime[j] == 0)
            {
                stu[i] ^= (1 << j);
                x /= prime[j];
            }
        }
    }
    f[0][0] = 1;
    for (int i = 1; i <= 70; i++)
    {
        for (int j = 0; j < (1 << 19); j++)
        {
            if (!cnt[i])
                f[i][j] = f[i - 1][j];
            else
            {
                f[i][j] = (f[i][j] + jie[cnt[i] - 1] * f[i - 1][j] % mod) % mod;
                f[i][j ^ stu[i]] = (f[i][j ^ stu[i]] + jie[cnt[i] - 1] * f[i - 1][j] % mod) % mod;
            }
        }
    }
    f[70][0]--;
    if (f[70][0] < 0)
        f[70][0] += mod;
    cout << f[70][0] << endl;

    return 0;
}

 

 

转载于:https://www.cnblogs.com/zbtrs/p/8046194.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值