【Luogu4448】 [AHOI2018初中组]球球的排列

题意

\(n\) 个球球,每个球球有一个属性值 。一个合法的排列满足不存在相邻两个球球的属性值乘积是完全平方数。求合法的排列数量对 \(10^9+7\) 取膜。

\(n\le 300\) (本题数据范围可扩大至 \(n\le 3000\)) 。

题解

首先很显然,如果 \(xy,yz\) 是完全平方数,那么 \(xz\) 也是完全平方数。这样我们可以将球球分成若干组,每组的两两乘积都是完全平方数。

那么问题转化为有若干球球,每个球球一个颜色,求满足相同颜色的球球不相邻的排列数。

下设 \(a_i\) 为第 \(i\) 种颜色的球球个数, \(m\) 为颜色个数。

我们考虑容斥。对于每种颜色,我们将相邻的小球球合并成一个大球球,设 \(b_i\) 为大球球的数量,那么答案即为 \(b\) 的可重排列数乘上每种颜色内部排列数 \(\displaystyle \frac{(\sum_{i=1}^mb_i)!}{\prod_{i=1}^mb_i!} \cdot \prod_{i=1}^m a_i!\) ,容斥系数用插板法计算,为 \(\displaystyle \prod_{i=1}^m (-1)^{a_i-b_i} \cdot \binom{a_i-1}{b_i-1}\)

考虑如何计算这个式子。设 \(s=\sum_{i=1}^m b_i\) ,那么答案式子可转化为:
\[ (-1)^{n-s} s!\prod_{i=1}^{m}\frac{\binom{a_i-1}{b_i-1}\cdot a_i!}{b_i!} \]
我们考虑用 dp 计算后半部分式子。设 \(f(i,j)\) 表示前 \(i\) 个数, \(\sum_{x=1}^i b_x=j\) 的方案数。枚举 \(b_i=k\) 转移:
\[ f(i,j)=\sum_{k=1}^{\min(a_i,j)}f(i-1,j-k)\cdot \frac{\binom{a_i-1}{k-1}\cdot a_i!}{k!} \]
最后按上面的式子枚举 \(s\) 容斥统计答案即可。

dp 的复杂度看上去像 \(n^3\) ,实际上理性分析,复杂度为 \(\sum_{i=1}^n a_i (\sum_{j=1}^i a_j) =O(n^2)\) .

code

#include<cstdio>
#include<cmath>
typedef long long ll;
const int N=305,Mod=1e9+7;
int t[N],a[N],f[N][N],n,m,fac[N],inv[N],ans,v;
inline int mul(int x, int y) {
    return 1ll*x*y%Mod;
}
inline int po(int x, int y)
{
    int r=1;
    while(y)
    {
        if(y&1) r=mul(r,x);
        x=mul(x,x), y>>=1;
    }
    return r;
}
inline int C(int x, int y) {
    return mul(mul(fac[x],inv[y]),inv[x-y]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1,x,j;i<=n;++i)
    {
        scanf("%d",&x);
        for(j=1;j<=m;++j)
            if(1ll*t[j]*x==(ll)sqrt(1ll*t[j]*x)*(ll)sqrt(1ll*t[j]*x))
            {
                ++a[j];
                break;
            }
        if(j>m) t[++m]=x,++a[m];
    }
    fac[0]=inv[0]=1;
    for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
    inv[n]=po(fac[n],Mod-2);
    for(int i=n-1;i;--i) inv[i]=mul(inv[i+1],i+1);
    int ans=0,v=0;
    f[0][0]=1;
    for(int i=1;i<=m;++i)
    {
        v+=a[i];
        for(int j=1;j<=v;++j)
            for(int k=1;k<=a[i]&&k<=j;++k)
                f[i][j]=(f[i][j]+mul(f[i-1][j-k],mul(C(a[i]-1,k-1),inv[k])))%Mod;
    }
    for(int i=1;i<=n;++i)
    {
        int t=mul(fac[i],f[m][i]);
        (n-i&1)?ans=(ans+Mod-t)%Mod:ans=(ans+t)%Mod;
    }
    for(int i=1;i<=m;++i) ans=mul(ans,fac[a[i]]);
    printf("%d",ans);
}

转载于:https://www.cnblogs.com/farway17/p/10972387.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值