BZOJ3294 [CQOI2011]放棋子

58 篇文章 0 订阅

Address


Solution

  • 因为不同颜色的棋子不能在同一行或者同一列,所以每种颜色的棋子的摆放是相对独立的。
  • 于是考虑设计这么一个状态 f[i][j][k] f [ i ] [ j ] [ k ] ,表示用前 k k 种颜色的棋子占领了任意 i j j 列的方案数,则:(假设第 k 种棋子有 a[k] a [ k ] 枚) f[i][j][k]=l=0i1r=0j1f[l][r][k1]×a[k](il)(jr)×Cilnl×Cjrmr((il)×(jr)a[k]) f [ i ] [ j ] [ k ] = ∑ l = 0 i − 1 ∑ r = 0 j − 1 f [ l ] [ r ] [ k − 1 ] × 用 a [ k ] 枚 棋 子 占 领 ( i − l ) 行 ( j − r ) 列 的 方 案 数 × C n − l i − l × C m − r j − r ( ( i − l ) × ( j − r ) ≥ a [ k ] )
  • 其它都好处理,但“用 a[k] a [ k ] 枚棋子占领 (il) ( i − l ) (jr) ( j − r ) 列的方案数”似乎不是那么容易直接求。
  • 考虑再设一个状态 g[i][j][k] g [ i ] [ j ] [ k ] ,表示任意 k k 枚同色棋子占领了任意 i j j 列的方案数,通过一些转换,我们可以得到:

(7)g[i][j][k]=(8)=(9)=Ci×jkl=1ir=1jg[l][r][k]×Cil×Cjr(l<i||r<j,i×jk)

  • 我们预处理 g[i][j][k] g [ i ] [ j ] [ k ] ,则 f[i][j][k] f [ i ] [ j ] [ k ] 的转移就可表示为: f[i][j][k]=l=0i1r=0j1f[l][r][k1]×g[il][jr][a[k]]×Cilnl×Cjrmr((il)×(jr)a[k]) f [ i ] [ j ] [ k ] = ∑ l = 0 i − 1 ∑ r = 0 j − 1 f [ l ] [ r ] [ k − 1 ] × g [ i − l ] [ j − r ] [ a [ k ] ] × C n − l i − l × C m − r j − r ( ( i − l ) × ( j − r ) ≥ a [ k ] )
  • 不一定要每行每列都占领,但棋子要全放完,答案就为 i=1nj=1mf[i][j][c] ∑ i = 1 n ∑ j = 1 m f [ i ] [ j ] [ c ]
  • 时间复杂度 O(n2m2c) O ( n 2 m 2 c )

Code

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

typedef long long ll;
const int Mod = 1e9 + 9;
const int N = 35, M = 15, L = 905;
int g[N][N], f[N][N][M], c[L][L];
int x, n, m, C, Ans;

int main()
{
//  freopen("chess.in", "r", stdin);
//  freopen("chess.out", "w", stdout);

    scanf("%d%d%d", &n, &m, &C);

    const int tmp = n * m;
    for (int i = 0; i <= tmp; ++i)
        c[i][0] = 1;
    for (int i = 1; i <= tmp; ++i)
        for (int j = 1; j <= i; ++j)
            c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % Mod;

    f[0][0][0] = 1;
    for (int k = 1; k <= C; ++k) 
    {
        scanf("%d", &x);
        memset(g, 0, sizeof(g));
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j)
            if (i * j >= x)
            {
                g[i][j] = c[i * j][x];
                for (int l = 1; l <= i; ++l)
                    for (int r = 1; r <= j; ++r)
                    if (l < i || r < j)
                        g[i][j] = ((ll)g[i][j] - (ll)g[l][r] 
                        * c[i][l] % Mod * c[j][r] % Mod + Mod) % Mod;
            }

        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j)
                for (int l = 0; l < i; ++l)
                    for (int r = 0; r < j; ++r)
                    {
                        int tx = i - l, ty = j - r;
                        if (tx * ty >= x)
                            (f[i][j][k] += (ll)f[l][r][k - 1] * g[tx][ty] % Mod 
                            * c[n - l][tx] % Mod * c[m - r][ty] % Mod) %= Mod;
                    }
    }

    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            (Ans += f[i][j][C]) %= Mod;
    printf("%d\n", Ans);

//  fclose(stdin); fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值