P6076 [JSOI2015]染色问题 (两层容斥)

文章介绍了JSOI2015编程竞赛中的一道染色问题,涉及一个n×m的棋盘,需要使用C种颜色进行染色,确保每行每列至少有一个染色的小方格。简化问题为只有黑白两种颜色时,利用容斥原理计算方案数量。进一步扩展到C种颜色,同样应用容斥原理解决。文章给出了具体的数学公式和递推关系,并提供了C++代码实现。
摘要由CSDN通过智能技术生成

原题链接:P6076 [JSOI2015]染色问题

题意

萌萌家有一个棋盘,这个棋盘是一个 n × m n \times m n×m 的矩形,分成 n n n m m m 列共 n × m n \times m n×m 个小方格。
现在萌萌和南南有 C C C 种不同颜色的颜料,他们希望把棋盘用这些颜料染色,并满足以下规定:

  1. 棋盘的每一个小方格既可以染色(染成 C C C 种颜色中的一种),也可以不染色。
  2. 棋盘的每一行至少有一个小方格被染色。
  3. 棋盘的每一列至少有一个小方格被染色。
  4. 每种颜色都在棋盘上出现至少一次。

1 ≤ n , m , c ≤ 400 1\le n,m,c\le 400 1n,m,c400

只考虑黑白

首先考虑一个简化的问题,即只有黑白两种颜色:给一个 n × m n\times m n×m 的矩形,给其中一些各自染成黑色,要求每一行每一列都至少有一个小方格是黑色,求方案数量。

f ( x ) f(x) f(x) 为:钦定有 x x x 列不染色,剩余的 m − x m-x mx 列 $ n$ 行部分保证每一行都至少有一个格子染色的方案数量。

那么通过容斥可以得出这个子问题的答案
a n s = f ( 0 ) − f ( 1 ) + f ( 2 ) … ans = f(0)-f(1)+f(2)\dots ans=f(0)f(1)+f(2)

f ( x ) f(x) f(x) 的表达式也比较容易得出
f ( x ) = ( m x ) ( 2 m − x − 1 ) n f(x) = {m\choose x}(2^{m-x}-1)^n f(x)=(xm)(2mx1)n

简要介绍这个式子的含义。 ( m x ) {m\choose x} (xm) 是从 m m m 列中钦定 x x x 列不染色。 2 m − x − 1 2^{m-x}-1 2mx1 是每一行的方案数(-1是因为不能为空)。套上 n n n 次幂就是 n n n 行的方案数量。

考虑 c c c 种颜色

g ( t ) g(t) g(t) 表示:钦定不使用 t t t 种颜色,完成上述问题的方案数。

那么答案通过容斥可以得出
a n s = g ( 0 ) − g ( 1 ) − g ( 2 ) … ans = g(0)-g(1)-g(2)\dots ans=g(0)g(1)g(2)

g ( t ) g(t) g(t) 的求法其实就是上一个问题。

仍然设钦定 x x x 列不染色,剩余的 m − x m-x mx 列 $ n$ 行部分保证每一行都至少有一个格子染色的方案数量为 f ( t ) f(t) f(t),容易得出
f t ( x ) = ( m x ) ( ( c − t ) m − x − 1 ) n f_t(x) = {m\choose x}((c-t)^{m-x}-1)^n ft(x)=(xm)((ct)mx1)n

这个式子的意义同上,只不过把 2 2 2 换成了 c − t c-t ct,表示可以使用的颜色数量。

所以有
g ( t ) = f t ( 0 ) − f t ( 1 ) + f t ( 2 ) … g(t) = f_t(0) - f_t(1) +f_t(2)\dots g(t)=ft(0)ft(1)+ft(2)
综上,本题需要两层容斥得出答案。

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int, int>


int Testnum = 1;

/**********************  Core code begins  **********************/

const int N = 1003, MOD = 1e9 + 7;

int n, m, c;
int C[N][N];


void init() {
    for (int i = 0; i < N; i++) {
        C[i][0] = 1;

        for (int j = 1; j <= i; j++) {
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
        }
    }
}


int qpow(int x, int k) {
    int res = 1;

    while (k) {
        if (k & 1) {
            res = res * x % MOD;
        }

        k /= 2;
        x = x * x % MOD;
    }

    return res;
}



// 至少有 x 种颜色没有使用,即使用了不超过 k = c - x 种颜色
int work(int x) {
    int k = c - x;
    int res = 0, sig = 1;

    // 枚举不超过 i 列有涂色,再做一遍容斥。
    for (int i = m; i >= 1; i--) {
        res = (res + C[m][i] * qpow((qpow(k + 1, i) - 1), n) % MOD * sig % MOD) % MOD;
        sig = MOD - sig;
    }

    res = res * C[c][x] % MOD;

    return res;
}



void SolveTest() {
    cin >> n >> m >> c;

    init();

    int res = 0, sig = 1;

    for (int i = 0; i <= c; i++) {
        res = (res + work(i) * sig % MOD) % MOD;
        // printf("work(%lld) == %lld\n", i, work(i));
        sig = MOD - sig;
    }

    cout << res;

}

/**********************  Core code ends  ***********************/


signed main() {

#ifdef LOCAL
    freopen("in.txt", "r", stdin);
#endif

    // cin >> Testnum;

    for (int i = 1; i <= Testnum; i++) {
        SolveTest();
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值