codeforces841E - On the Bench

20 篇文章 0 订阅
2 篇文章 0 订阅

题面在这里
题目大意:
给一个长度为n序列,n <= 300,问你有多少种排列方法,使得任意相邻两个数字的乘积都不是完全平方数。

做法:
很巧妙的DP+组合计数。
需要将数字分组。我们考虑将所有数字去掉平方因子后的数(相当于一个数开根号化成最简根式后根号里的数)相同的分到一组。
这个其实等价于,将所有数分解质因数后质因子的指数奇偶性相同的分到一组。
还等价于,两两之间相乘会变成平方数的分成一组。(额说到这了才是重点咳咳咳。。之前的帮助理解)
于是将问题转化为,同组的数字不能相邻的方案数。

然后我们按照这个组别进行dp。设f[i][j]表示前i组有j对同组相邻的数的方案数,记cnt[i]表示第i组数的个数。
这里我们假设同组的数都是无序的。(最后再乘上每组的全排列数即可)
转移的时候,考虑插入一组新的数,首先将这组的数分成k段,然后将其中的p段插入到之前j对同组相邻的数中,剩下的k-p段随意。
转移方程如下:
f[i][j+cnt[i]kp]+=f[i1][j]C(cnt[i]1,k1)C(j,p)C(m1j+2,kp);

需要好好理解qwq。

代码如下:

/*************************************************************
    Problem: codeforces 841E - On the Bench
    User: fengyuan
    Language: C++
    Result: Accepted
    Time: 46 ms
    Memory: 2800 KB
    Submit_Time: 2017-12-06 11:02:30
*************************************************************/

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cctype>
#include<vector>
#include<map>
#include<queue>
#include<string>
#define rep(i, x, y) for (int i = (x); i <= (y); i ++)
#define down(i, x, y) for (int i = (x); i >= (y); i --)
#define mid ((l+r)/2)
#define lc (o<<1)
#define rc (o<<1|1)
#define pb push_back
#define mp make_pair
#define PII pair<int, int>
#define F first
#define S second
#define B begin()
#define E end()
using namespace std;
typedef long long LL;
//head

const int N = 305;
const int MOD = 1e9 + 7;
int n, tot, m;
int a[N], f[N][N], fac[N], C[N][N], cnt[N];
bool vis[N];

inline void prepare()
{
    rep(i, 0, N-1) C[i][0] = 1;
    rep(i, 1, N-1)
        rep(j, 1, i) C[i][j] = (C[i-1][j] + C[i-1][j-1]) % MOD;
    fac[0] = 1;
    rep(i, 1, N-1) fac[i] = 1LL*fac[i-1]*i%MOD;
}

inline bool check(LL x)
{
    return floor(sqrt(x)) == sqrt(x);
}

int main()
{
    scanf("%d", &n);
    prepare();
    rep(i, 1, n) scanf("%d", &a[i]);
    rep(i, 1, n){
        if (vis[i]) continue; vis[i] = 1; cnt[++ tot] = 1;
        rep(j, i+1, n)
            if (check(1LL*a[i]*a[j])) vis[j] = 1, cnt[tot] ++;
    }
    f[1][cnt[1]-1] = 1; m = cnt[1];
    rep(i, 2, tot){
        rep(j, 0, m-1)
            rep(k, 1, cnt[i])
                rep(p, 0, k){
                    if (p > j) break;
                    f[i][j+cnt[i]-k-p] += 1LL*f[i-1][j]*C[cnt[i]-1][k-1]%MOD*C[j][p]%MOD*C[m-1-j+2][k-p]%MOD;
                    f[i][j+cnt[i]-k-p] %= MOD;
                }
        m += cnt[i];
    }
    int ans = f[tot][0];
    rep(i, 1, tot) ans = 1LL*ans*fac[cnt[i]]%MOD;
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值