[CQOI2011]放棋子

[CQOI2011]放棋子

\(m\times n\)的棋盘中放入一些棋子,棋子由c种颜色组成,第i种颜色的棋子数有\(d_i\)个,要求每一行每一列只能有同种颜色的棋子或没有棋子,询问其方案数\(mod\ 10^9+7\),N,M<=30 C<=10 总棋子数<=250。

显然为错排问题,问题比较复杂,优先考虑递推方程,要表现出棋子的颜色种类,又要表现出行列,故设\(f[i][j][k]\)表示前k种颜色选了任意i行j列的方案数,又设\(g[i][j][k]\)表示k枚棋子放入i行j列网格图的方案数,注意到g不好划分和依据策略转移,于是考虑补集,不难有

\[g[i][j][k]=C_{ij}^k-\sum_{x=1}^i\sum_{y=1}^jg[x][y][k](x<i||y<j) \]

注意到k是固定无须枚举,于是边算边求解,省得无用求解。

现在考虑f,不难知道它是与前k-1中颜色有关,而且还要知道这种颜色选了x,y列,并且还要保证装得下,于是有

\[f[i][j][k]=\sum_{x=1}^{i-1}\sum_{y=1}^{j-1}f[x][y][k-1]C_{m-x}^{i-x}C_{n-y}^{j-y}g[i-x][j-y][d[k]](xy\geq d[k]) \]

边界:\(f[0][0][0]=1\)

套用递推转移即可。

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define il inline
#define ri register
#define ll long long
#define yyb 1000000009
using namespace std;
ll c[905][905],g[31][31],dp[31][31][11],ans;
int main(){
    int n,m,color,i,j,k,l,r,gzy;
    scanf("%d%d%d",&n,&m,&color);
    for(i=0;c[i][0]=1,i<=900;++i)
        for(j=1;j<=i;++j)c[i][j]=(c[i-1][j]+c[i-1][j-1])%yyb;
    dp[0][0][0]=true;
    for(k=1;k<=color;++k){
        scanf("%d",&gzy);
        for(i=1,memset(g,0,sizeof(g));i<=n;++i)
            for(j=1;j<=m;++j){
                g[i][j]=c[i*j][gzy];
                for(l=1;l<=i;++l)
                    for(r=1;r<j;++r)
                        (g[i][j]-=g[l][r]*c[i][l]%yyb*c[j][r])%=yyb;
                for(l=1;l<i;++l)(g[i][j]-=g[l][r]*c[i][l]%yyb*c[j][r])%=yyb;
                (g[i][j]+=yyb)%=yyb;
            }
        for(i=1;i<=n;++i)
            for(j=1;j<=m;++j)
                for(l=0;l<i;++l)
                    for(r=0;r<j;++r)
                        (dp[i][j][k]+=dp[l][r][k-1]*c[n-l][i-l]%yyb*c[m-r][j-r]%yyb*g[i-l][j-r])%=yyb;
    }for(l=1;l<=n;++l)for(r=1;r<=m;++r)(ans+=dp[l][r][color])%=yyb;printf("%lld",ans);
    return 0;
}

posted @ 2019-05-02 22:13  a1b3c7d9  阅读(195)  评论(0编辑  收藏  举报