简略题意:给出n个数,要求把他们重新排列之后,没有相邻的数的乘积是完全平方数。
两数相乘为完全平方数有两种可能。
1.两数本身都是完全平方数,等价于两个数的质因数的幂次都为偶数。
2.两个数的质因数都有奇数项,但相乘后(等价于对应幂次相加)为偶数。
我们只考虑第二种情况即可,用一个数对应的幂次为奇数的质因子代表这个数。那么两数不能相邻当前仅当两数不相同。原问题等价于,给出n个数,要求没有两个相同的数相邻。
我们得到的新问题是容斥原理的一个经典模型,其答案等价于:
A1: 没有限制条件的方案数 - A2:有一对相同的数相邻的方案数 + A3:有两对相同的数相邻的方案数 -
......
用
dp[i]
表示原数组被分成i块,每块的数相同的方案数。
则答案为
Ans=dp[n]∗n!−dp[n−1]∗(n−1)!+......
对于这个
dp[i]
, 我们每次只要枚举当前的数分成多少块即可转移。
转移方程为:
dp[i]=∑min(cnt,i)j=1dp[i−j]∗fac[cnt]∗inv[fac[j]](cnt−1j−1)。
cnt为当前数出现的次数。
#include <bits/stdc++.h>
#define all(x) x.begin(), x.end()
using namespace std;
typedef long long LL;
const LL maxn = 310;
const LL mod = 1e9+7;
LL n, v;
LL dp[2][maxn];
LL C[maxn][maxn];
LL fac[maxn], invfac[maxn];
LL inv(LL a) {
return a == 1? 1 : ((mod-mod/a)*inv(mod%a))%mod;
}
void init() {
for(LL i = 0; i <= 300; i++) {
C[i][0] = 1;
for(LL j = 1; j <= i; j++) {
C[i][j] = C[i-1][j-1] + C[i-1][j];
if(C[i][j] >= mod) C[i][j] -= mod;
}
}
fac[0] = invfac[0] = 1;
for(int i = 1; i <= 300; i++)
fac[i] = fac[i-1] * i % mod, invfac[i] = inv(fac[i]);
}
map<vector<LL>, LL> M;
int main() {
init();
scanf("%lld", &n);
for(LL i = 1; i <= n; i++) {
scanf("%lld", &v);
vector<LL> V;
for(LL d = 2; d * d <= v; d++) {
if(v % d == 0) {
LL exp = 0;
while(v % d == 0) v/=d, exp^=1;
if(exp) V.push_back(d);
}
}
if(v > 1) V.push_back(v);
M[V]++;
}
dp[0][0] = 1;
LL _ = 0;
for(auto t : M) {
_++;
LL now = _ & 1, pre = now ^ 1;
memset(dp[now], 0, sizeof dp[now]);
LL cnt = t.second;
for(LL i = 1; i <= n; i++) {
for(LL j = 1; j <= min(i, cnt); j++) {
dp[now][i] += (dp[pre][i-j] * (((fac[cnt] * invfac[j])%mod * C[cnt-1][j-1])%mod))%mod;
if(dp[now][i] >= mod) dp[now][i] %= mod;
}
}
}
LL ans = 0;
for(LL i = 1; i <= n; i++) {
ans += ((((n - i & 1)?(mod-1):1) * fac[i])%mod * dp[_&1][i])%mod;
if(ans >= mod) ans -= mod;
}
printf("%lld\n", ans);
return 0;
}