CF840C On the Bench 组合计数+dp

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

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

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

所以,总转移方程式为:

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 )

因为 i = 1 c n t n u m i = n ,所以,总复杂度为 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]);
} 

猜你喜欢

转载自blog.csdn.net/liangzihao1/article/details/81380784
今日推荐