CF840C On the Bench(dp+组合数学)

首先我们对n个数进行一个分组,把乘积为平方数的数分在一组(可以证明:同一组内的数两两乘积为平方数,不同组的两个数乘积一定不是平方数),记作一共分了nn组,则同组的数只要不相邻即可满足题意。即原问题转化为 n个物品,分成了nn组,要求同组物品不能相邻,问共有几种排列方案。我们dp解决这个问题,f[i][j]表示前i组,有j个相邻的同组的方案数。
我们现在考虑插入第i组,第i组的大小为size[i],前面已经填了tot个数,我们把第i组分成k部分插入tot+1个空档(已经填的数两两之间和开头结尾称为空档),插入的k个空档中有D个空档两边为同组的数(则插入后消去了这D个相邻的同组),则有
f[i][j-D+size[i]-k]+=f[i-1][j]*T。(原来有j个相邻的同组,消去了D个,这次新添进来size[i]-k个相邻的同组)
T表示这种插入方式的种数,为 Ck1size1CDjCkDtot+1j 。(乘法原理:把size个数分成k部分的种数* 在j个可以消去的相邻同组中选D个的种数 * 在tot+1-j个空当中选k-D个空档的种数(另外D个必须填在所选的D个相邻同组之间))
初值f[0][0]=1,答案就是f[nn][0]。
这是所有的组的排列方案,但是我们还没有考虑具体的每组的数的顺序,所以答案再乘上所有的组的排列可能即每组size的阶乘。
这种题的原型就是 n个物品,分成了nn组,要求同组物品不能相邻,问共有几种排列方案。可以变换成多种题,套路是一样的。

#include <bits/stdc++.h>
#define N 305
#define ll long long
int const mod=1e9+7;
int n,a[N],size[N],nn=0,f[N][N],C[N][N];//f[i][j]表示前i组,有j个相邻的同组 
ll fact[N];
bool vis[N];
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
inline bool check(ll x){
    ll xx=sqrt(x);
    if(xx*xx==x) return 1;
    else return 0;
}
inline void calc(){//预处理组合数和阶乘 
    C[0][0]=1;
    for(int i=1;i<=n;++i){
        C[i][0]=1;
        for(int j=1;j<=i;++j)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
    fact[0]=1;
    for(int i=1;i<=n;++i) fact[i]=(ll)fact[i-1]*i%mod;
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();for(int i=1;i<=n;++i) a[i]=read();
    for(int i=1;i<=n;++i){
        if(vis[i]) continue;
        vis[i]=1;size[++nn]=1;
        for(int j=i+1;j<=n;++j)
            if(check((ll)a[i]*a[j])) vis[j]=1,size[nn]++;
    }
    calc();int tot=0;f[0][0]=1;
    for(int i=1;i<=nn;++i){
        for(int j=0;j<=tot;++j)//dp[i-1][j]
            for(int k=1;k<=size[i]&&k<=tot+1;++k)//把第i组分成k部分
                for(int D=0;D<=k&&D<=j;++D)//消掉D个连续的同组
                    f[i][j-D+size[i]-k]=(f[i][j-D+size[i]-k]+(ll)f[i-1][j]*C[size[i]-1][k-1]%mod*C[j][D]%mod*C[tot+1-j][k-D])%mod;
        tot+=size[i];
    }
    ll ans=f[nn][0];
    for(int i=1;i<=nn;++i) ans=ans*fact[size[i]]%mod;//乘上排列总数 
    printf("%lld\n",ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值