题目链接
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;
}