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

在這裏提供三個版本(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;
}


發佈了24 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章