CF840C On the Bench 组合计数+dp

题目大意:
给你 n(n<=300) n ( n <= 300 ) 个数,问有多少个排列满足任意相邻两个数的积不为完全平方数。(相同数字交换位置算不同的排列,可以知道,总排列数一定有 n! n ! 个)。

分析:
如果 ab a ∗ b 为完全平方数, ac a ∗ c 为完全平方数,那么 bc b ∗ c 也为完全平方数。
证明:对于任意一个数 x x ,质因数分解后如果每一个质数的指数都是偶数(0也算偶数),那么这个数就是完全平方数。根据每一个质数指数的奇偶性,我们可以写出一个二进制数,记为 bitx b i t x 。而两数相乘,其质数的指数相加,对于奇偶性就是异或。因为, bita xor bitb=0 b i t a   x o r   b i t b = 0 bita xor bitc=0 b i t a   x o r   b i t c = 0 ,所以 bitb=bitc b i t b = b i t c ,即 bitb xor bitc=0 b i t b   x o r   b i t c = 0 。因此, bc b ∗ c 也是完全平方数。

所以我们可以把两两可以组成完全平方数的数分成 cnt c n t 组,每组大小为 numi n u m i
我们设 f[i][j] f [ i ] [ j ] 为前 i i 组,有j个冲突点(即相邻两个数积为完全平方数)的方案数。因为可以随意调换,所以第 i i 组有numi!个方案。我们再枚举 k k ,表示把第i个组分成 k k 块,即放k1个挡板,方案数为 (k1numi1) ( k − 1 n u m i − 1 ) ,这样会产生 numik n u m i − k 个不合法位置。
sum s u m 为前 i1 i − 1 个组数的个数,这 k k 个块可以放入sum+1个位置上,而其中 j j 个位置是不合法位置,枚举有l个不非法位置中间插入了当前组的块,这样就会减少 l l 个不合法位置,也使用了l个块。在 j j 个位置中选择l个位置,方案数为 (lj) ( l j ) 。在剩下的 sum+1j s u m + 1 − j 个位置中选择剩下的 kl k − l 个块,方案数为 (klsum+1j) ( k − l s u m + 1 − j )

所以,总转移方程式为:

f[i][jl+numik]=numi!(k1numi1)(lj)(klsum+1j) f [ i ] [ j − l + n u m i − k ] = ∑ n u m i ! ∗ ( k − 1 n u m i − 1 ) ∗ ( l j ) ∗ ( k − l s u m + 1 − j )

因为 cnti=1numi=n ∑ i = 1 c n t n u m i = n ,所以,总复杂度为 O(n3) O ( n 3 )

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#define LL long long

const LL maxn=307;
const LL mod=1e9+7;

using namespace std;

LL num[maxn],a[maxn];
LL f[maxn][maxn];
LL n,cnt,x,sum;
LL jc[maxn],njc[maxn];

LL power(LL x,LL y)
{
    if (y==1) return x;
    LL c=power(x,y/2);
    c=(c*c)%mod;
    if (y%2) c=(c*x)%mod;
    return c;
}

LL C(LL x,LL y)
{
    if ((x<0) || (y<0) || (y>x)) return 0;
    if ((x==0) || (y==x))  return 1;
    return jc[x]*njc[y]%mod*njc[x-y]%mod;
}

int main()
{
    scanf("%lld",&n);
    for (LL i=1;i<=n;i++)
    {
        scanf("%lld",&x);
        LL flag=0;
        for (LL j=1;j<=cnt;j++)
        {
            LL c=trunc(sqrt(x*a[j]));
            if (c*c==x*a[j])
            {
                flag=1;
                num[j]++;
                break;
            }
        }
        if (!flag) num[++cnt]=1,a[cnt]=x;
    }
    jc[0]=jc[1]=1;
    njc[0]=njc[1]=1;
    for (LL i=2;i<=n;i++)
    {
        jc[i]=(LL)(jc[i-1]*i)%mod;
        njc[i]=power(jc[i],mod-2);
    }   
    f[0][0]=1;
    for (LL i=1;i<=cnt;i++)
    {
        for (LL j=0;j<=sum;j++)
        {
            for (LL k=1;k<=num[i];k++)
            {
                for (LL l=0;l<=j;l++)
                {
                    LL d=j-l+num[i]-k;
                    if ((d>=0) && (d<n))
                    {
                        f[i][d]=(f[i][d]+jc[num[i]]*C(num[i]-1,k-1)%mod*C(j,l)%mod*C(sum+1-j,k-l)%mod*f[i-1][j]%mod)%mod;
                    }
                }
            }
        }
        sum+=num[i];
    }
    printf("%I64d\n",f[cnt][0]);
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值