在这里提供三个版本(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,至少一个(因为每行至少一个)
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;
}