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

来源:互联网 发布:微信刷步软件 编辑:程序博客网 时间:2024/05/17 20:01

在这里提供三个版本(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 __int64using 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 __int64using 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
原创粉丝点击