• [Hdu-5155] Harry And Magic Box[思维题+容斥,计数Dp]


    Online JudgeHdu5155

    Label:思维题+容斥,计数Dp

    题面:

    题目描述

    给定一个大小为(N*M)的神奇盒子,里面每行每列都至少有一个钻石,问可行的排列方案数。由于答案较大,输出对(1e9+7)取模后的结果。

    输入

    多组数据。每组数据读入两个整数(N,M)
    (0≤N,M≤50)

    输出

    每组数据输出一行表示答案。

    样例

    Input

    1 1
    2 2
    2 3
    

    Output

    1
    7
    25
    

    Hint

    There are 7 possible arrangements for the second test case.
    They are:
    11
    11

    11
    10

    11
    01

    10
    11

    01
    11

    01
    10

    10
    01

    Assume that a grids is '1' when it contains a jewel otherwise not.

    题解

    1.做法一(计数Dp)

    定义状态(dp[i][j])表示已经摆放好了前i行(前面i行都合法,即每行都放了至少一个钻石),并且有j列上已经摆放了至少一个钻石。答案很明显是(dp[n][m])

    转移到第(i)行时,

    1、枚举当前准备在第i行放的钻石个数(k)

    2、其中有(z)个摆在了之前没有钻石的列上(也就是说这z个钻石给哪些之前没有钻石的列做出贡献)

    3、再枚举一个之前已经有钻石的列数(j)(j∈[k-z,m-z])

    关于j的范围:下限,既然有z个摆在了之前没有钻石的列上,那就有(k-z)个放在有钻石的列上。上限,除了那(z)列没放钻石,其他(m-z)列已经放了。

    则有(dp[i][j+t]+=dp[i-1][j]*当前行方案数)。当前行的方案数由两部分组成:一是要将这(z)个钻石放在之前没有钻石的列上——(c[m-j][z]);二是要将剩余的(k-z)个钻石放在之前已经有钻石的列上——(c[j][m-z])。根据乘法原理,两者相乘即可。

    综上,该算法时间复杂度为(O(N^2+case*N^4))。((N^2)为前面组合数的预处理)。

    #include<bits/stdc++.h>
    #define For(a,b,c) for(register int a=b;a<=c;++a)
    #define mod 1000000007
    using namespace std;
    int n,m;
    long long dp[55][55],c[55][55];
    void pre(){
    	c[0][0]=1;
    	for(int i=1;i<=50;i++)c[i][0]=c[i][i]=1;
    	for(int i=2;i<=50;i++){
    		for(int j=1;j<i;j++)c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    	} 	
    }
    int main(){
    	pre();
    	while(~scanf("%d%d",&n,&m)){
    		memset(dp,0,sizeof(dp));
    		For(i,1,m)dp[1][i]=c[m][i];
    		For(i,2,n)For(k,1,m)For(z,0,k)For(j,k-z,m-z){
    			dp[i][j+z]+=dp[i-1][j]*c[j][k-z]%mod*c[m-j][z]%mod;
    			dp[i][j+z]%=mod;
    		}
    		printf("%lld
    ",dp[n][m]);
    	}
    }
    

    2.做法二(容斥)

    这种做法就比较优秀了。

    题目是让我们求每一行每一列都至少有一个钻石的方案数,也即求,有0列一颗钻石都没有、其他列随意的方案数(前提是每一行都至少有一个钻石——这样才能保证行这一维上也合法,这一点我们可以在后面人为控制)。

    有0列不太好求,将其转化成至少有0列然后再通过容斥进行去重。

    定义(f(i))表示填完整个盒子后,至少有(i)列一个钻石都没有、其他列随意,的方案数。

    • 为什么"至少"这个问法比较可做,原因在于我们可以人为地选定m列中的i列,强制让这几列一个钻石都不放,剩余的随意即可——如果改成"有”,我们还得人为控制剩下的m-i列,情况就变得复杂了。

    很容易得出(f(i)=C(m,i)*每一行的摆列方案数^n),至于每一类的摆列方案数,如果没有限制的话就是(2^{m-i})了,但是之前说了,我们还得保证行上至少有一个钻石,所以去掉全是0(这一行不放钻石)的这种情况。所以最后$$f(i)=C(m,i)*(2{m-i}-1)n$$

    最后根据容斥原理(ans=f(0)-f(1)+f(2)-f(3)+...f(m))

    总的时间复杂度为(O(N^2+case*MlogN))

    //下面其实可以预处理2的幂的
    #include<bits/stdc++.h>
    #define For(a,b,c) for(register int a=b;a<=c;++a)
    #define mod 1000000007
    using namespace std;
    typedef long long ll;
    int n,m;
    ll c[55][55];
    void pre(){
    	c[0][0]=1;
    	for(int i=1;i<=50;i++)c[i][0]=c[i][i]=1;
    	for(int i=2;i<=50;i++){
    		for(int j=1;j<i;j++)c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    	} 	
    }
    ll ksm(ll r,int d){
    	ll res=1;
    	while(d){
    		if(d&1)res=res*r%mod;
    		r=r*r%mod;d>>=1;
    	}
    	return res;
    }
    int main(){
    	pre();
    	while(~scanf("%d%d",&n,&m)){
    		ll ans=0;
    		for(int i=0;i<=m;i++){
    			//从m中选择i列永远不放钻石,剩余的情况随意安排 
    			ll val=c[m][i]*ksm(ksm(2,m-i)-1,n)%mod;
    			if(i%2==0)ans=(ans+val)%mod;
    			else ans=(ans-val+mod)%mod;
    		}
    		printf("%lld
    ",ans%mod);
    	}
    }
    

    update20191024

    这道题的(n,m)开的比较小,只有(50),所以上面两个做法都直接(N^2)预处理组合数了,但显然的,可以不用预处理组合数,可以直接(O(NlogN))预处理出阶乘,模逆元(带个log是快速幂的),这样利用容斥就可以在(O(MlogN))的时间内解决此问题,(n,m)可以开到(3000)左右。

  • 相关阅读:
    QML中的信号和函数
    Hangfire入门(任务调度)
    js 批量设置css样式
    phpstudy配置伪静态的方法
    mysql 文件导入方法总结
    通过配置Mysql参数提高写入速度
    phpmyadmin导入数据库大小限制修改
    易语言入门之一 简介
    判断移动设备、触屏
    Compiler Error Message: CS0016: Could not write to output file 回绝访问
  • 原文地址:https://www.cnblogs.com/Tieechal/p/11443317.html
Copyright © 2020-2023  润新知