zoukankan      html  css  js  c++  java
  • BZOJ 3294: [Cqoi2011]放棋子 容斥+组合

    比较头疼的计数题.
    我们发现,放置一个棋子会使得该棋子所在的1个行和1个列都只能放同种棋子.
    定义状态 $f_{i,j,k}$ 表示目前已使用了 $i$ 个行,$j$ 个列,并放置了前 $k$ 种棋子的方案数.
    假设当前枚举到的是第 $k$ 个棋子,该种棋子有 $num_{k}$ 个.
    枚举 $d1,d2$ 表示安排这 $num_{k}$ 个棋子需要用 $d1$ 个行,$d2$ 个列.
    可以将 $d1$ 个行和 $d2$ 个列并到一起,这就构成了一个 $d1 imes d2$ 的矩形.
    在这个矩形中要选取 $num_{k}$ 个棋子,且 $d1$ 个行和 $d2$ 个列中每一个行和列都至少要有一个棋子.
    我们像要求这个东西的方案数.
    分析到这步就卡住了,我想的递推式不够优秀.
    同届神犇 $JZYshurak$ 给了一个容斥的解决方案 :
    令 $g_{i,j,k}$ 表示在 $i imes j$ 的矩阵中安放第 $k$ 种颜色,且每个行和列都有棋子的方案数.
    正着求不好求,考虑容斥:$C_{i imes j}^{num_{k}}-sum_{x=1}^{i}sum_{y=1}^{j}C_{i}^{x} imes C_{j}^{y} imes g_{x,y,k}$.
    这个容斥的意义: 总的方案 - 不合法方案.
    那么不合法方案就是 $num_{k}$ 个棋子覆盖的行和列都小于 $i$ 与 $j$ 的方案总和,还要乘一下组合数,因为矩形是我们拼凑的,实际中这个矩形的行和列都是散落的.
    综上,$f_{i,j,k}=f_{i-d1,j-d2,k-1} imes C_{n-i+d1}^{d1} imes C_{m-j+d2}^{d2} imes g_{d1,d2,k}$.
    时间复杂度为 $O(cn^2m^2)$

    #include <cstdio> 
    #include <algorithm>
    #define N 33 
    #define mod 1000000009
    #define ll long long 
    #define setIO(s) freopen(s".in" , "r" , stdin) 
    using namespace std;           
    int num[N], n , m, c;     
    ll f[N][N][13], fac[1000], inv[1000], G[N][N][10];     
    inline ll qpow(ll base, ll k) 
    {
        ll tmp = 1ll; 
        for( ; k ; base = (base * base) % mod , k >>= 1) if(k & 1) tmp = (tmp * base) % mod;
        return tmp;  
    }
    inline ll C(int a, int b) 
    {
        return fac[a] * inv[b] % mod * inv[a - b] % mod;
    }
    inline void init() 
    {
        int i , j; 
        f[0][0][0] = 1ll; 
        fac[0] = inv[0] = 1;
        for(i = 1; i <= n * m ; ++ i) 
        {
            fac[i] = (fac[i - 1] * i) % mod ; 
            inv[i] = qpow(fac[i] , mod - 2);   
        }        
    } 
    inline void Getg() 
    {
        int i , j , k , x, y;                               
        for(k = 1; k <= c; ++ k) 
        {
            for(i = 1; i <= n ; ++ i) 
            {
                for(j = 1; j <= m ; ++ j) 
                {  
                    if(i * j < num[k]) continue;  
                    G[i][j][k] = C(i * j , num[k]);                   
                    for(x = 1; x <= i ; ++ x) 
                    {
                        for(y = 1; y <= j ; ++ y) 
                        {
                            if(x * y < num[k] || (x == i && y == j)) continue;         
                            G[i][j][k] = (G[i][j][k] - (C(i, x) * C(j, y) % mod * G[x][y][k] % mod) + mod) % mod;   
                        }
                    }
                }
            }
        } 
    }
    inline int up(int a, int b)
    {
        if(a % b == 0) return a / b; 
        else return (a / b) + 1; 
    }
    int main() 
    { 
        int i , j ,  k; 
        ll re = 0; 
        scanf("%d%d%d",&n , &m , &c); 
        for(i = 1; i <= c ; ++ i) scanf("%d", &num[i]); 
        init(), Getg();      
        for(k = 1; k <= c ; ++ k) 
        {
            for(i = 1; i <= n ; ++ i) 
                for(j = 1; j <= m ; ++ j) 
                {
                    int d1, d2; 
                    for(d1 = 1; d1 <= num[k]; ++ d1) 
                    {
                        if(d1 > i) break;    
                        for(d2 = up(num[k], d1); d2 <= num[k]; ++ d2) 
                        {
                            if(d2 > j) break;        
                            ll t = f[i - d1][j - d2][k - 1] * C(n - i + d1, d1) % mod * C(m - j + d2, d2) % mod * G[d1][d2][k] % mod;   
                            f[i][j][k] = (f[i][j][k] + t) % mod;    
                        }
                    }
                    if(k == c)
                    {
                        re = (re + f[i][j][k]) % mod; 
                    }
                } 
        }
        printf("%lld
    ", re); 
        return 0; 
    }
    

      

  • 相关阅读:
    2021年2月4号
    2021年2月3号
    2021年2月2号
    2021年2月1日
    2021年1月31日
    2021年1月30日
    20171205xlVBA往返航班组合
    选择文件
    从VBA过渡到Python
    20171114xlVba选定单行记录并打印
  • 原文地址:https://www.cnblogs.com/guangheli/p/11362370.html
Copyright © 2011-2022 走看看