Codeforces-840C On the Bench(dp)

 

C. On the Bench

time limit per test

2 seconds

memory limit per test

256 megabytes

input

standard input

output

standard output

A year ago on the bench in public park Leha found an array of n numbers. Leha believes that permutation p is right if for all 1 ≤ i < ncondition, that api·api + 1 is not perfect square, holds. Leha wants to find number of right permutations modulo 109 + 7.

Input

First line of input data contains single integer n (1 ≤ n ≤ 300) — length of the array.

Next line contains n integers a1, a2, ... , an (1 ≤ ai ≤ 109) — found array.

Output

Output single integer — number of right permutations modulo 109 + 7.

Examples

input

3
1 2 4

output

2

input

7
5 2 4 2 4 1 1

output

144

Note

For first example:

[1, 2, 4] — right permutation, because 2 and 8 are not perfect squares.

[1, 4, 2] — wrong permutation, because 4 is square of 2.

[2, 1, 4] — wrong permutation, because 4 is square of 2.

[2, 4, 1] — wrong permutation, because 4 is square of 2.

[4, 1, 2] — wrong permutation, because 4 is square of 2.

[4, 2, 1] — right permutation, because 8 and 2 are not perfect squares.

 

 

 

题解:dp

一个简单的性质:a*b为完全平方数,b*c为完全平方数,那么a*c也是完全平方数

将两两相乘等于完全平方数的数放入一组,设一共有sz组,每一组有cnt[i]个数

设dp[i][j]为当前一共放了i组数,有j对连续的数是同一组的(即其中需要插入某个不是同一组的数,以下称作间隙)

设q为将第i组数分成q份,则其中有cnt[i]-q个间隙

设p为从这q份中选p份插入间隙中,其它的不插入间隙

设m为前i组数一共有m个数字,有m+1个位置可以插入数字

k = j - p + cnt[i] - q

dp[i][k]=\sum_{j=0}^{m-1} dp[i - 1][j]*cnt[i]!*C_{q-1}^{cnt[i]-1}*C_{p}^{j}*C_{q-p}^{m+1-j}

#include<bits/stdc++.h>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define x first
#define y second
#define rep(i,a,b) for(int i=a;i<(b);++i)
#define per(i,a,b) for(int i=a-1;i>=(b);--i)
#define fuck(x) cout<<'['<<#x<<' '<<(x)<<']'
#define add(x,y) x=((x)+(y)>=mod)?(x)+(y)-mod:(x)+(y)
#define sub(x,y) x=((x)-(y)<0)?(x)-(y)+mod:(x)-(y)
#define clr(a,b) memset(a,b,sizeof(a))
#define eps 1e-10
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int> VI;
typedef pair<int, int> PII;
const int INF = 0x3f3f3f3f;
const ll INFLL = 0x3f3f3f3f3f3f3f3fLL;
const int mod = 1e9 + 7;
const int MX = 305;

ll F[MX], invF[MX];
ll power(ll a, ll b) {
    ll ret = 1;
    while(b) {
        if(b & 1) ret = (ret * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return ret;
}
void init() {
    F[0] = 1;
    for(int i = 1; i < MX; i++) F[i] = (F[i - 1] * i) % mod;
    invF[MX - 1] = power(F[MX - 1], mod - 2);
    for(int i = MX - 2; i >= 0; i--) {
        invF[i] = invF[i + 1] * (i + 1) % mod;
    }
}
ll C(int n, int m) {
    if(n < 0 || m < 0 || m > n) return 0;
    if(m == 0 || m == n)    return 1;
    return F[n] * invF[n - m] % mod * invF[m] % mod;
}


ll a[MX];
int cnt[MX], vis[MX];

bool check(ll x) {
    ll l = 1, r = 1e9;
    while(l <= r) {
        ll m = (l + r) >> 1;
        if(m * m == x) return 1;
        if(m * m < x) l = m + 1;
        else r = m - 1;
    }
    return r * r == x;
}
ll dp[MX][MX];
int pre[MX];
inline ll mul(ll a, ll b, ll c) {return a * b % mod * c % mod;}
int main() {
#ifdef local
    freopen("in.txt", "r", stdin);
#endif // local
    init();
    int n; cin >> n;
    rep(i, 0, n) cin >> a[i];
    int sz = 0;
    rep(i, 0, n) if(!vis[i]) {
        rep(j, i, n) if(!vis[j] && check(a[i]*a[j])) cnt[sz]++, vis[j] = 1;
        sz++;
    }
    pre[0] = cnt[0]; rep(i, 1, sz) pre[i] = pre[i - 1] + cnt[i];
    dp[0][cnt[0] - 1] = F[cnt[0]];
    rep(i, 1, sz) {
        rep(j, 0, pre[i - 1]) rep(q, 1, cnt[i] + 1) rep(p, 0, min(j + 1, q + 1)) {
            int k = j - p + cnt[i] - q;
            dp[i][k] += dp[i - 1][j] * mul(C(cnt[i] - 1, q - 1), C(j, p), C(pre[i - 1] + 1 - j, q - p)) % mod;
            if(dp[i][k] >= mod) dp[i][k] -= mod;
        }
        rep(j, 0, pre[i]) dp[i][j] = dp[i][j] * F[cnt[i]] % mod;
    }
    cout << dp[sz - 1][0] << endl;

    return 0;
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值