题意
给定序列 a [ 1... n ] a[1...n] a[1...n],求有多种 a a a 的排列,满足任意两个相邻的数乘积不是完全平方数,答案对 1 0 9 + 7 10^9+7 109+7 取模。
a i ≤ 1 0 9 , 1 ≤ n ≤ 300 a_i\le 10^9,\ 1\le n \le 300 ai≤109, 1≤n≤300
转换
如果 x y = p 2 xy=p^2 xy=p2, y z = q 2 yz=q^2 yz=q2,那么 x z = ( p q x ) 2 xz=({\dfrac{pq}{x}})^2 xz=(xpq)2,也是一个完全平方数。所以我们可以把把 x , y , z x,y,z x,y,z 视为一个同一种颜色,以此类推……用并查集维护每一种颜色的数量。
于是问题转换为:
有 m m m 种颜色的球,第 i i i 种颜色的球有 s i s_i si 种,总共 n n n 个球,求同色不相邻的排列的数量。
DP + 容斥 O ( n 2 ) O(n^2) O(n2)
本方法参考 这篇博客。
设 f ( k ) f(k) f(k) 表示把这些小球分成 k k k 个连续段的方案数,其中:
- 每一段都是相同的颜色;
- 段内小球考虑排列顺序;
- 段与段之间无序,即所有的段构成一个无序集合。
下面用 DP 来求 f ( k ) f(k) f(k)。
设
f
(
i
,
k
)
f(i,k)
f(i,k) 表示考虑前
i
i
i 种颜色,分成了
k
k
k 组的方案数。容易推出
f
f
f 的转移方程
f
(
i
,
k
)
=
∑
j
=
1
min
(
s
i
,
k
)
f
(
i
−
1
,
k
−
j
)
⋅
s
i
!
⋅
(
s
i
−
1
j
−
1
)
j
!
f(i,k) = \sum_{j=1}^{\min(s_i, k)}f(i-1,k-j)\cdot \dfrac{s_i!\cdot{s_i-1\choose j-1}}{j!}
f(i,k)=j=1∑min(si,k)f(i−1,k−j)⋅j!si!⋅(j−1si−1)
简要介绍这个式子的含义。
枚举最后一种颜色(第 i i i 种颜色)分成了 j j j 段,所以由 f ( i − 1 , k − j ) f(i-1,k-j) f(i−1,k−j) 转移过来。
s i ! s_i! si! 是将 s i s_i si 个同色小球排列,然后 ( s i − 1 j − 1 ) {s_i-1\choose j-1} (j−1si−1) 是将这个排列分成 k k k 个段(原理是隔板法)。由于组与组是无序的,所以要除以 j ! j! j!。
那么 f ( n , k ) f(n,k) f(n,k) 就是将所有的小球分为 k k k 组的方案数,简要记为 f ( k ) f(k) f(k)。
特别注意一下这个 DP 式子的复杂度,它虽然是三重循环,但神奇的是它的复杂度其实是 O ( n 2 ) O(n^2) O(n2) 。因为第一层循环的是颜色,第三层循环的次数不超过该种颜色的数量,所以这两层循环的总次数其实是 ∑ i = m n s i \sum_{i=m}^{n}s_i ∑i=mnsi,这两层和一块是 O ( n ) O(n) O(n) 级别的。
如果把所有小球分为 k k k 组,那么至少有 n − k n-k n−k 对相邻的同色小球。
所以
f
(
n
)
⋅
n
!
f(n)\cdot n!
f(n)⋅n! 就是提前钦定
n
−
k
n-k
n−k 对同色小球,其余随意组合的方案数之和。这样答案就是典型的容斥
a
n
s
=
f
(
n
)
⋅
n
!
−
f
(
n
−
1
)
⋅
(
n
−
1
)
!
+
…
ans=f(n)\cdot n! - f(n-1)\cdot(n-1)! + \dots
ans=f(n)⋅n!−f(n−1)⋅(n−1)!+…
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
int Testnum = 1;
/********************** Core code begins **********************/
const int N = 1003, MOD = 1e9 + 7;
int n, a[N];
int fac[N], inv[N], invfac[N];
int m = 0, s[N], f[N][N];
struct DSU {
int fa[N], siz[N];
void init() {
for (int i = 1; i < N; i++) {
fa[i] = i;
siz[i] = 1;
}
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
if (find(x) == find(y)) {
return;
}
int fx = find(x), fy = find(y);
fa[fx] = fy;
siz[fy] += siz[fx];
}
} dsu;
void init() {
fac[0] = inv[0] = invfac[0] = 1;
fac[1] = inv[1] = invfac[1] = 1;
for (int i = 2; i < N; i++) {
fac[i] = fac[i - 1] * i % MOD;
inv[i] = ((MOD - MOD / i * inv[MOD % i]) % MOD + MOD) % MOD;
invfac[i] = invfac[i - 1] * inv[i] % MOD;
}
}
int C(int x, int y) {
if (x < y || x < 0) {
return 0;
}
return fac[x] * invfac[y] % MOD * invfac[x - y] % MOD;
}
void SolveTest() {
cin >> n;
dsu.init();
init();
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int x = sqrt(a[i] * a[j]);
if (x * x == a[i] * a[j]) {
dsu.merge(i, j);
}
}
}
m = 0;
for (int i = 1; i <= n; i++) {
if (dsu.fa[i] == i) {
s[++m] = dsu.siz[i];
}
}
f[0][0] = 1;
for (int i = 1; i <= m; i++) {
for (int k = i; k <= n; k++) {
for (int j = 1; j <= min(s[i], k); j++) {
f[i][k] = (f[i][k] + f[i - 1][k - j] * fac[s[i]] % MOD
* C(s[i] - 1, j - 1) % MOD * invfac[j] % MOD) % MOD;
}
}
}
int res = 0, sig = 1;
for (int i = n; i >= 1; i--) {
res = (res + sig * f[m][i] % MOD * fac[i] % MOD) % MOD;
sig = MOD - sig;
}
cout << res;
}
/********************** Core code ends ***********************/
signed main() {
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif
// cin >> Testnum;
for (int i = 1; i <= Testnum; i++) {
SolveTest();
}
return 0;
}