bzoj 3294: [Cqoi2011]放棋子 (容斥原理+组合数+DP)

题目描述

传送门

题目大意:在一个n*m的棋盘中放入一些颜色不同的棋子,每个格子最多只能放一个棋子,不同颜色的棋子不能放在同一行或同一列,求合法的方案数。

题解

相当于每行每列只能被一种颜色占据。
那么我们可以给每个颜色分配行列数。 g[p][i][j] 表示第p中颜色占据i行j列的方案数。
如果能求出g,那么我们就可以做二维背包.
f[t][i+k][j+l]+=f[t1][i][j]g[t][k][l]C[ni][k]C[mj][l]
如何求g,直接用组合数可能会使算出的方案存在空行或空列,所以我们需要容斥一下。
g[p][i][j]=C[ij][c]C[i][x]C[j][y]g[p][x][y] 其中x,y不能同时等于i,j。

代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define N 33
#define LL long long 
#define p 1000000009
using namespace std;
LL C[1003][1003],g[N][N][N],f[N][N][N];
int n,m,k;
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%d%d",&n,&m,&k);
    for (int i=0;i<=1000;i++) C[i][0]=1;
    for (int i=1;i<=1000;i++)
     for (int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%p;
    for (int t=1;t<=k;t++) {
        int c; scanf("%d",&c);
        for (int i=0;i<=n;i++)
         for (int j=0;j<=m;j++) {
            g[t][i][j]=C[i*j][c];
            for (int k=0;k<=i;k++)
             for (int l=0;l<=j;l++) 
              if (k!=i||l!=j) g[t][i][j]=(g[t][i][j]-C[i][k]*C[j][l]%p*g[t][k][l]%p)%p;
         }
    }
    f[0][0][0]=1;
    for (int t=1;t<=k;t++)
     for (int i=0;i<=n;i++)
      for (int j=0;j<=m;j++)
       for (int k=0;k<=n;k++)
        for (int l=0;l<=m;l++)
         if (i+k<=n&&j+l<=m) {
            f[t][i+k][j+l]+=f[t-1][i][j]*g[t][k][l]%p*C[n-i][k]%p*C[m-j][l]%p;
            f[t][i+k][j+l]%=p;
         } 
    LL ans=0;
    for (int i=0;i<=n;i++)
     for (int j=0;j<=m;j++) ans=(ans+f[k][i][j])%p;
    printf("%I64d\n",(ans%p+p)%p);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值