Codeforces 840C [DP]

Description

给定 n 个数,求n个数所成的排列中不存在 ai×ai+1 为完全平方数的方案数。 n300

Solution

首先先把每个数的平方因子全部去掉,就形成了 vn 个等价类,每个大小为 vi ,那么只要计算出这些等价类内不考虑顺序的答案 ans ,最后答案就是 ansvni=1vi!
考虑DP。
dpi,p 为前 p 个等价类,存在i个不合法的 pairs 的方案数。

  • 枚举从已选的非法的 pairs 的个数 i 。(总共usd个)
  • 枚举将待选的数分成 j 块。(总共c个)
  • 枚举从 j 块中取k块。

把这 k 块插入到非法的i pairs 中,就只有 ik+(cj) 个非法的 pairs 啦,所以就可以转移到 dpik+cj,p+1 ,贡献就是:

dpi,p(c1j1)(ik)(usdi+1jk)

  • (c1j1) c 个数形成c1 pairs j 个数形成j1 pairs
  • (ik) 是在 i 个中选k的贡献。
  • (usdi+1jk) 是把那 k 块数插到i个数中间之后,有 jk 个数可以随便插在 usdi+1 个位置中。

为了保证上述枚举的可行性(即把数插入的时候有足够的位置可以插入),将等价类按大小降序排序后直接DP就好了。
然后这个是可以滚动数组存一存的。。。
有一些小细节,可以看代码QAQ。

#include <map>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

inline char get(void) {
    static char buf[100000], *S = buf, *T = buf;
    if (S == T) {
        T = (S = buf) + fread(buf, 1, 100000, stdin);
        if (S == T) return EOF;
    }
    return *S++;
}
inline void read(int &x) {
    static char c; x = 0; int sgn = 0;
    for (c = get(); c < '0' || c > '9'; c = get()) if (c == '-') sgn = 1;
    for (; c >= '0' && c <= '9'; c = get()) x = x * 10 + c - '0';
    if (sgn) x = -x;
}

typedef long long ll;
const int N = 303;
const int MOD = 1000000007;

int C[N][N];
int dp[N], dp1[N];
int n, vn, usd, c, ans;
int a[N], mp[N], v[N];
map<int, int> buc;

inline int Mod(int x) {
    while (x >= MOD) x-= MOD;
    return x;
}
inline void Add(int &x, int y) {
    x += y; x = Mod(x);
}

int main(void) {
    freopen("1.in", "r", stdin);
    read(n);
    C[0][0] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < n; j++)
            C[i][j] = (j == 0 ? 1 : Mod(C[i - 1][j - 1] + C[i - 1][j]));
    for (int i = 0; i < n; i++) {
        read(a[i]);
        for (int j = 2; j * j <= a[i]; j++)
            while (a[i] % (j * j) == 0)
                a[i] /= j * j;
        if (!buc[a[i]]) mp[vn++] = a[i];
        ++buc[a[i]];
    }
    for (int i = 0; i < vn; i++) v[i] = buc[mp[i]];
    sort(v, v + vn);
    reverse(v, v + vn);
    usd = v[0];
    dp[usd - 1] = 1;
    for (int it = 1; it < vn; it++) {
        c = v[it];
        for (int i = usd + c; i >= 0; i--) dp1[i] = 0;
        for (int i = 0; i < usd; i++) {
            if (dp[i] == 0) continue;
            for (int j = 1; j <= c; j++)
                for (int k = 0; k <= i && k <= j; k++)
                    if (usd - i + 1 >= j - k)
                        Add(dp1[i - k + c - j], (ll)dp[i] * C[c - 1][j - 1] % MOD * C[i][k] % MOD * C[usd - i + 1][j - k] % MOD);
        }
        usd += c;
        for (int i = 0; i <= usd; i++) dp[i] = dp1[i];
    }
    ans = dp[0];
    for (int i = 0; i < vn; i++)
        for (int j = 1; j <= v[i]; j++)
            ans = (ll)ans * j % MOD;
    printf("%d\n", ans);
    return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值