• 题解 CF840C On the Bench


    题目链接

    如果(a imes b)是完全平方数,(a imes c)是完全平方数,那么(a^2 imes b imes c)也是完全平方数,又因为(a^2)是完全平方数,所以(b imes c)也是完全平方数。也就是说,完全平方数具有传递性。据此,我们可以把给定的序列划分为若干个集合,使得同一集合内的数两两的乘积都是完全平方数,不同集合内的任意两个数乘积都不是完全平方数。

    于是,问题转化为,(n)个数分为(m)个集合。求有多少种(n)个数的排列方式,使得任意相邻两个数不来自同一集合。

    设集合(i)的大小为(sz[i]),前(i)个集合的大小之和为(sum[i])

    DP。逐个集合考虑。设(dp[i][j]),表示考虑了前(i)个集合,有(j)个不合法的位置的方案数。这里“不合法的位置”,是指某个位置上的数,和它前一个位置上的数,来自同一个集合。显然,有(j)个不合法的位置,也意味着序列被划分成了(sum[i]-j),每个块内的元素来自相同的集合,相邻两块的元素来自不同的集合。

    注意,在这个DP状态的定义下,我们认为,这(sum[i])个数是紧贴着放置的,但是在转移时,可能会在上一状态下两个紧贴着的位置之间插入一些数。

    转移时,我们先考虑把当前集合,分成多少个。设这个块数为(k)(1leq kleq sz[i])),枚举(k)。显然,把(x)个元素划分为(k)个块并排在一起的方案数为(x!cdot {x-1choose k-1})(插板法)。这样,当前集合就划分好了。考虑如何把这划分出的(k)个块插入前面已经排列好的序列中。

    前面的序列里已经有了(sum[i-1]-j)个块。如果在不同块之间插入,或者在整个序列的最前面/最后面插入,都不会使不合法位置的数量(即DP数组的第二维)减少;否则,在原先某一块的内部插入时,会使一个本来不合法的数,和它前面的位置隔开,于是这个数就变得合法了。我们枚举新增的(k)个块里,有(l)块是第一种情况,剩下的块是第二种情况。显然,(lleq k)(lleq sum[i-1]-j+1)

    这样,我们会转移到(dp[i][j+(sz[i]-k)-(k-l)])。其中((sz[i]-k))是新增的不合法的位置,((k-l))是因为新增的数把原来的数隔开,从而使得不合法位置的数量减少了。

    于是,可以写出转移式:

    [dp[i][j-(sz[i]-k)-(k-l)] exttt{+=} dp[i-1][j]cdot(sz[i]!){sz[i]-1choose k-1}cdot{sum[i-1]-j+1choose l}{jchoose k-l} ]

    答案就是(dp[m][0])

    下面来分析一下时间复杂度。我们一共枚举了(i),(j),(k),(l)四个东西,看起来是(O(n^4))。但是注意到,(i)遍历的是集合数量,(k)遍历的是当前集合的大小,(i),(k)套起来是(O(sum_{t=1}^{m}sz[t])=O(n))的。所以实际的时间复杂度为(O(n^3))

    参考代码:

    //problem:CF840C
    #include <bits/stdc++.h>
    using namespace std;
    
    #define pb push_back
    #define mk make_pair
    #define lob lower_bound
    #define upb upper_bound
    #define fi first
    #define se second
    #define SZ(x) ((int)(x).size())
    
    typedef unsigned int uint;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    
    namespace Fread{
    const int MAXN=1<<20;
    char buf[MAXN],*S,*T;
    inline char getchar(){
    	if(S==T){
    		T=(S=buf)+fread(buf,1,MAXN,stdin);
    		if(S==T)return EOF;
    	}
    	return *S++;
    }
    }//namespace Fread
    #ifdef ONLINE_JUDGE
    	#define getchar Fread::getchar
    #endif
    template<typename T>inline void read(T& x){
    	x=0;int f=1;
    	char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch))x=x*10+(ch-'0'),ch=getchar();
    	x*=f;
    }
    /*  ------  by:duyi  ------  */ // myt天下第一
    const int MAXN=300,MOD=1e9+7;
    inline int mod1(int x){return x<MOD?x:x-MOD;}
    inline int mod2(int x){return x<0?x+MOD:x;}
    inline void add(int &x,int y){x=mod1(x+y);}
    inline void sub(int &x,int y){x=mod2(x-y);}
    inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
    
    int fac[MAXN+5],ifac[MAXN+5],n,a[MAXN+5],fa[MAXN+5],sz[MAXN+5],s[MAXN+5],cnt,dp[MAXN+5][MAXN+5];
    inline int comb(int n,int k){
    	if(n<k)return 0;
    	return (ll)fac[n]*ifac[k]%MOD*ifac[n-k]%MOD;
    }
    
    int get_fa(int x){return (x==fa[x])?x:(fa[x]=get_fa(fa[x]));}
    void union_s(int x,int y){
    	x=get_fa(x);y=get_fa(y);
    	if(x!=y){
    		if(sz[x]>sz[y])swap(x,y);
    		fa[x]=y;sz[y]+=sz[x];
    	}
    }
    
    bool is_square(ll x){ll t=sqrt(x);return t*t==x;}
    int g(int n,int k){return (ll)fac[n]*comb(n-1,k-1)%MOD;}
    int main() {
    	fac[0]=1;
    	for(int i=1;i<=MAXN;++i)fac[i]=(ll)fac[i-1]*i%MOD;
    	ifac[MAXN]=pow_mod(fac[MAXN],MOD-2);
    	for(int i=MAXN-1;i>=0;--i)ifac[i]=(ll)ifac[i+1]*(i+1)%MOD;
    	
    	read(n);
    	for(int i=1;i<=n;++i)fa[i]=i,sz[i]=1;
    	for(int i=1;i<=n;++i){
    		read(a[i]);
    		for(int j=1;j<i;++j)if(is_square((ll)a[i]*a[j]))union_s(i,j);
    	}
    	for(int i=1;i<=n;++i)if(get_fa(i)==i)s[++cnt]=sz[i];
    	
    	//for(int i=1;i<=cnt;++i)cout<<s[i]<<" ";cout<<endl;
    	dp[1][s[1]-1]=fac[s[1]];
    	for(int i=2,sum=s[1];i<=cnt;++i){
    		for(int j=0;j<=sum-1;++j)if(dp[i-1][j]){
    			//有j个不合法的元素: 被分成了sum-j个连续段
    			for(int k=1;k<=s[i]&&k<=sum+1;++k){
    				for(int l=max(0,k-j);l<=sum-j+1&&l<=k;++l){
    					assert(j+s[i]-k-(k-l)>=0);
    					add(dp[i][j+(s[i]-k)-(k-l)],(ll)dp[i-1][j]*g(s[i],k)%MOD*comb(sum-j+1,l)%MOD*comb(j,k-l)%MOD);
    				}
    			}
    		}
    		sum+=s[i];
    	}
    	cout<<dp[cnt][0]<<endl;
    	//int i,j;while(1){read(i);read(j);cout<<dp[i][j]<<endl;}
    	return 0;
    }
    
  • 相关阅读:
    <自动化测试>之<使用unittest Python测试框架进行参数化测试>
    <自动化测试>之<unittest框架使用1>
    <自动化测试>之<selenium API 查找元素操作底层方法>
    <自动化测试>之<selenium API 用法2>
    <自动化测试>之<Selenium API 的用法1>
    <Jmeter入门不放弃>之<3.两种常见录制脚本的方法>
    <Jmeter入门不放弃>之<2.常用功能>
    <Jmeter入门不放弃>之<1.认识jmeter>
    <自动化测试>之<SeleniumIDE使用详解 >
    sql 注入get与post模式语句
  • 原文地址:https://www.cnblogs.com/dysyn1314/p/12657000.html
Copyright © 2020-2023  润新知