CF895C: Square Subsets题解

题目链接

http://codeforces.com/contest/895/problem/C

题意

从n个数中选取不少于1个数, 使得这些数的乘积为平方数的方案数.

分析

注意到这n个数都为不超过70的正整数, 由算数基本定理可得这n个数一共最多需要19种素数来构造, 如:
17 = 17
36 = 2 * 2 * 3 * 3
70 = 2 * 5 * 7
注意到, 若一个数是平方数, 则构造它的每种素数的个数为偶数个, 如36 = 2 * 2 * 3 * 3.
于是可构造DP[i][j]: 从所有不超过i的数里面, 选出符合状态j的方案数, 其中状态j是个19位的数字, 从右到左第i位表示第i为素数的选择情况, 若为1, 表示该素数选择了奇数次, 否则, 表示该素数选择了偶数次. 转移方程见代码, 最终答案为DP[70][0]. 由于状态很多, 所以需要使用滚动数组优化空间复杂度, 否则会MLE.

代码


#include<bits/stdc++.h>
#define x first
#define y second
#define ok puts("ok");
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const double PI = acos(-1.0);
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const int N=1e5+9;
const int shift=1e3+9;
const double Eps=1e-7;

const int mod = 1e9+7;

ll dp[2][1<<20], n, d, cnt[79], f1[79], f0[79], mask[79];

ll kuaisumi(ll a, ll b) {
    ll ans = 1;
    while(b) {
        if(b & 1) ans = ans * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return ans;
}

bool isprime(int d) {
    for(int i = 2; i <= sqrt(d); i++)
        if(d%i == 0) return false;
    return true;
}

void init() {
    int cnt = 0;
    for(int i = 2; i <= 70; i++) {
        if(!isprime(i)) continue;
        for(int j = 1; j <= 70; j++) {
            int x = j;
            while(x%i == 0) {
                x/=i;
                mask[j] ^= 1<<cnt;
            }
        }
        cnt++;
    }
}

int main(void) {
    if(fopen("in", "r")!=NULL) {freopen("in", "r", stdin); freopen("out", "w", stdout);}
    init();
    while(cin >> n) {
        memset(cnt, 0, sizeof cnt);
        for(int i = 0; i < n; i++) {
            scanf("%lld", &d);
            cnt[d]++;
        }
        for(int i = 1; i <= 70; i++) {
            if(cnt[i] == 0) 
                f1[i] = 0, f0[i] = 1;
            else
                f1[i] = f0[i] = kuaisumi(2, cnt[i]-1);
        }
        memset(dp, 0, sizeof dp);
        dp[0][0] = 1;
        int cur, nex;
        for(int i = 0; i < 70; i++) {
            cur = i&1, nex = 1 - cur;
            for(int j = 0; j < 1<<20; j++) {
                (dp[nex][j^mask[i+1]] += dp[cur][j] * f1[i+1]) %= mod;
                (dp[nex][j] += dp[cur][j] * f0[i+1]) %= mod;
            }
            for(int j = 0; j < 1<<20; j++)
                dp[cur][j] = 0;
        }
        printf("%lld\n", dp[nex][0]-1);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值