codeforces 840C On the Bench 容斥+DP

简略题意:给出n个数,要求把他们重新排列之后,没有相邻的数的乘积是完全平方数。

两数相乘为完全平方数有两种可能。
1.两数本身都是完全平方数,等价于两个数的质因数的幂次都为偶数。
2.两个数的质因数都有奇数项,但相乘后(等价于对应幂次相加)为偶数。

我们只考虑第二种情况即可,用一个数对应的幂次为奇数的质因子代表这个数。那么两数不能相邻当前仅当两数不相同。原问题等价于,给出n个数,要求没有两个相同的数相邻。

我们得到的新问题是容斥原理的一个经典模型,其答案等价于:
A1: 没有限制条件的方案数 - A2:有一对相同的数相邻的方案数 + A3:有两对相同的数相邻的方案数 - ......

dp[i] 表示原数组被分成i块,每块的数相同的方案数。
则答案为 Ans=dp[n]n!dp[n1](n1)!+......

对于这个 dp[i] , 我们每次只要枚举当前的数分成多少块即可转移。
转移方程为: dp[i]=min(cnt,i)j=1dp[ij]fac[cnt]inv[fac[j]](cnt1j1)
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值