hdu5155(Harry And Magic Box) DP+组合容斥原理 BC25

14 篇文章 0 订阅
4 篇文章 0 订阅

在这里提供三个版本(2DP+1组合&容斥原理)--在学校的我就是耐得住寂寞--   对于知识就是得fussy~~
链接:http://acm.hdu.edu.cn/showproblem.php?pid=5155

 
题意:nxm的棋盘,要求每行每列至少放一个棋子的方法数。
dp[i][j]表示前 i 行有 j 列都有了棋子,且每行也有棋子。fussy
 
官方:dp题,我们一行一行的考虑。dp[i][j],表示前i行,都满足了每一行至少有一个宝石的条件,而只有j列满足了有宝石的条件的情况有多少种。枚举第i+1行放的宝石数k,这k个当中有t个是放在没有宝石的列上的,那么我们可以得到转移方程:
dp[i+1][j+t]+=dp[i][j]*c[m-j][t]*c[j][k-t],其中c[x][y],意为在x个不同元素中无序地选出y个元素的所有组合的个数。
 
 
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define Mod 1000000007
#define lll __int64
using namespace std;
lll c[55][55];
lll dp[55][55];
void calc()
{
    for(int i=0;i<=51;i++)
    {
        c[i][0] = 1;
        for(int j=1;j<=i;j++)
            c[i][j] = (c[i-1][j-1]+c[i-1][j])%Mod;
        //组合公式c[i][j] = (c[i-1][j-1]+c[i-1][j]);
    }
}
int main()
{
    int n,m,i,j,k,t;
    calc();
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        for(i=1;i<=m;i++)dp[1][i]=c[m][i];
        for(i=1;i<=n;i++)               //第i行
        {
            for(k=1;k<=m;k++)           //1.枚举共本行放多少个
            {
                for(t=0;t<=k;t++)       //2.枚举新增个数
                {
                    for(j=max(1,k-t);j+t<=m;j++)
                    {
                        dp[i][j+t] = (dp[i][j+t]+dp[i-1][j]*c[m-j][t]%Mod*c[j][k-t]%Mod)%Mod;
/*
本次种数+=上次种数 * 产生新列组合种数(c[m-j][t]:在未覆盖的m-j列中选t列) * 不产生..(在已覆盖的j列中选k-t列)
c[m-j][t]枚举第i+1行放的宝石数k,这k个当中有t个是放在没有宝石的列(m-j)上的
c[j][k-t]表示剩下的k-t个在原来那j个有棋子的列去放,这样放不会增加至少有一个棋子的列。
1.枚举第i行放的宝石数k;2.这k个当中有t个是放在没有宝石的列上的
1和2是因为新增的个数t是这行内的k(1<=k<=n)中所包含,如新增t=1,可以是本行放2或3个时候的.(即枚举所有可能情况)
*/
                    }
                }
            }
        }
        printf("%I64d\n",dp[n][m]%Mod);
    }
    return 0;
}

这题做法: 从第1行到第n行,枚举这一行有k列已至少有一个,再枚举前一行有j列至少有一个,然后

枚举这一行新放多少个棋子t,至少一个(因为每行至少一个)


那么有 dp[i][k] += dp[i-1][j]*C[m-j][k-j]*C[j][t-(k-j)], C表示组合数
C[m-j][k-j]表示新增的那些原来没棋子现在有棋子的列k-j列分别可以放到上一行没放的地方m-j个地方,
这样放会增加至少有一个棋子的列
C[j][t-(k-j)]表示剩下的在原来那j个有棋子的列去放,这样放不会增加至少有一个棋子的列。
个人认为上面那个DP比较好理解。。
<pre name="code" class="cpp">#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define Mod 1000000007
#define lll __int64
using namespace std;
lll c[55][55];
lll dp[55][55];
void calc()
{
    for(int i=0;i<=51;i++)
    {
        c[i][0] = 1;
        for(int j=1;j<=i;j++)
            c[i][j] = (c[i-1][j-1]+c[i-1][j])%Mod;
        //组合公式c[i][j] = (c[i-1][j-1]+c[i-1][j]);
    }
}
int main()
{
    int n,m,i,j,k,t;
    calc();
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        dp[0][0] = 1;
        for(i=1;i<=n;i++)
        {
            for(k=1;k<=m;k++)           //这一行得多少个亮(稍微模拟即明)
            {
                for(j=0;j<=k;j++)       //上一行已有多少个亮(1至i-1行有多少列亮)
                {
                    for(t=max(1,k-j);t<=k;t++)   //枚举共本行放多少个
                    {
                        dp[i][k] = (dp[i][k]+dp[i-1][j]*c[m-j][k-j]%Mod*c[j][t-(k-j)]%Mod)%Mod;
//本次种数+=上次种数 * 产生新列组合种数(c[m-j][k-j]:在未覆盖的m-j列中选k-j列) * 不产生..(在已覆盖的j列中选t-(k-j)列)
//dp[i][k]:dp[i][j+(k-j)]+=dp[i-1][j]*c[][k-j]*c[][];
                    }
                }
            }
        }
        printf("%I64d\n",dp[n][m]%Mod);
    }
    return 0;
}

 
 

组合+容斥原理

/*
	在n*m的矩阵中,枚举每一行有i列不存在钻石
(注意枚举出的是i个1*n的小矩形且可以可拼成n*i的矩形,且这些必定是都不放)
(因为n=3时,选出的两列或许是1和3(被中间的2隔开了)
那么共有C(m,i)种排列。(从m列中选出的是i个1*n的小矩形)
	每行对于其他的m-i列中可以放也可以不放,但是要排除全都不放的情况,
得到每行有2^(m-i)-1种(-1:每行排除全不放),再加上n行得到(2^(m-i)-1)^n)
得到f(i) = C(m,i) * (2^(m-i)-1)^n ;
容斥原理排除多余的 f(0) - f(1) + f(2)-f(3)+...f(n-1) ;
n=2,m=3时,
f(0)=49:0列不放,即全列,则包含放两行的,故-f(1)
f(1)=27:1列不放,则包含放1行的两次(多-了次)(画图看看就明),故+f(2)
f(2)=3 :2列不放,即等于m
如此类推,发现当前-f(i)下次+回f(i+1)
//*/
#include<cstring>
#include<string>
#include<iostream>
#include<cmath>
#include<bitset>
using namespace std;
#define REPF( i , a , b ) for ( int i = a ; i <= b ; ++ i )
typedef __int64 LL;
typedef pair<int,int>pil;
const int maxn=55;
const int MOD=1000000007;
LL c[maxn][maxn];//2^(m-i)-1;
int n,m;
void init()
{
    REPF(i,1,50)
    {
        c[i][0]=c[i][i]=1;
        REPF(j,1,i-1)
           c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD;
    }
}
LL pow_mod(LL a,int b)
{
    a%=MOD;
    LL ans=1;
    while(b)
    {
        if(b&1)  ans=ans*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return ans;
}
int main()
{
    init();
    while(~scanf("%d%d",&n,&m))
    {
        LL ans=0;
		REPF(i,0,m-1)
        {
			LL tn=1,c1=m-i;//(1LL<<(m-i));
			while(c1--)tn<<=1;
            if(i&1)
                ans=(ans-(pow_mod(tn-1,n)*c[m][i])%MOD+MOD)%MOD;
            else
                ans=(ans+(pow_mod(tn-1,n)*c[m][i])%MOD)%MOD;
			//printf("i=%d  ans=%I64d  pow=%I64d  tn=%I64d\n",i,ans,
			//	(pow_mod(tn-1,n)),tn);
        }
        printf("%I64d\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值