@codeforces - 1237F@ Balanced Domino Placements

目录


@description@

给定一个 h 行 w 列的方格图,上面已经放置了一些 1*2 的多米诺骨牌。

我们称一个放置多米诺骨牌的方案是好的,当且仅当任何两个多米诺骨牌不占用相同的行与列。

现在需要你在方格图上新增若干(可以为 0)个多米诺骨牌,使得最后得到的方格图仍然是好的。保证初始给定的方格图一定是好的。
求方案数 mod 998244353。

Input
第一行包含 3 个整数 h, w, n (1≤h,w≤3600;0≤n≤2400)。
接下来 n 行,每行四个整数 ri,1,ci,1,ri,2,ci,2 (1≤ri,1≤ri,2≤h; 1≤ci,1≤ci,2≤w),描述了第 i 个多米诺骨牌占据的两个格子。保证两个格子不同且相邻。
保证初始局面是好的。

Output
输出放置若干多米诺骨牌并维持它是好的方案数 mod 998244353。

Examples
Input
5 7 2
3 1 3 2
4 4 4 5
Output
8

Input
5 4 2
1 2 2 2
4 3 4 4
Output
1

Note
第一个样例初始长成这样:
@codeforces - 1237F@ Balanced Domino Placements

可以放置的方案数有如下几种:
@codeforces - 1237F@ Balanced Domino Placements

第二个样例初始长成这样:
@codeforces - 1237F@ Balanced Domino Placements
不可以放置更多的多米诺。

@solution@

可以,我觉得神仙出的题就是很巧妙,但令人心服口服。

先考虑怎么计数,发现基本不可做。
因为断开的部分不能放置多米诺,所以也不能 dp。
也没有特殊的组合意义能够快速推出一个式子。

我们之所以无法快速计数,因为多米诺的位置是二维的,而二维状态最后可能变得很复杂。
我们可以尝试降维打击将问题分别转成行和列上一维问题,再尝试合并得到结果。

怎么转化呢?首先假如我们要选 x 个竖着的,y 个横着的。
对于行,相当于我们要选 2 个连续空余行共 x 个,1 个空余行共 y 个;列同理。
我们最后算方案的时候,只需要 2 个连续空余行匹配 1 个空余列,就会产生 x! 种方案,并且这 x! 中方案一定合法。同理,横着的将其占用的行与列匹配,也会产生 y! 种合法方案。

记 f[x][y] 表示选 2 个连续空余行共 x 个,1 个空余行共 y 个的方案数,g[x][y] 表示选 1 个空余列共 x 个,2 个连续空余列共 y 个的方案数。
则选 x 个竖着的,y 个横着的总方案数为 f[x][y]*g[x][y]*x!*y!。

那么怎么求 f[x][y]/g[x][y] 呢?先根据初始局面处理出哪些行可用,哪些列可用。假设共 cntH 行可用。
记 t[i][j] 表示前 i 行,已经选了 j 个竖着的方案数。这个很好 dp。
那么 f[x][y] = t[h][x] * C(cntH - 2*x, y)。g 同理可求。

总复杂度为 O(h*w)。

@accepted code@

#include<cstdio>
const int MAXN = 3600;
const int MOD = 998244353;
int c[MAXN + 5][MAXN + 5], fct[MAXN + 5];
int a[MAXN + 5], b[MAXN + 5];
int f[MAXN + 5][MAXN + 5], g[MAXN + 5][MAXN + 5];
int h, w, n;
void init() {
    for(int i=0;i<=MAXN;i++) {
        c[i][0] = 1;
        for(int j=1;j<=i;j++)
            c[i][j] = (c[i-1][j-1] + c[i-1][j])%MOD;
    }
    fct[0] = 1;
    for(int i=1;i<=MAXN;i++)
        fct[i] = 1LL*fct[i-1]*i%MOD;
}
int main() {
    init(); scanf("%d%d%d", &h, &w, &n);
    for(int i=1;i<=n;i++) {
        int r1, c1, r2, c2;
        scanf("%d%d%d%d", &r1, &c1, &r2, &c2);
        a[r1] = a[r2] = 1, b[c1] = b[c2] = 1;
    }
    a[0] = b[0] = 1;
    int cnth = 0, cntw = 0;
    for(int i=1;i<=h;i++) cnth += (1 - a[i]);
    for(int i=1;i<=w;i++) cntw += (1 - b[i]);
    f[0][0] = 1;
    for(int i=1;i<=h;i++) {
        for(int j=w;j>=0;j--) {
            f[i][j] = f[i-1][j];
            if( !a[i] && !a[i-1] && j )
                f[i][j] = (f[i][j] + f[i-2][j-1])%MOD;
        }
    }
    g[0][0] = 1;
    for(int j=1;j<=w;j++) {
        for(int i=h;i>=0;i--) {
            g[i][j] = g[i][j-1];
            if( !b[j] && !b[j-1] && i )
                g[i][j] = (g[i][j] + g[i-1][j-2])%MOD;
        }
    }
    int ans = 0;
    for(int i=0;i<=cnth;i++) // 横 
        for(int j=0;j<=cntw;j++) { // 纵 
            if( 2*j + i > cnth || 2*i + j > cntw ) continue;
            int del = 1LL*fct[i]*fct[j]%MOD;
            int tmp1 = 1LL*f[h][j]*c[cnth-2*j][i]%MOD;
            int tmp2 = 1LL*g[i][w]*c[cntw-2*i][j]%MOD;
            del = 1LL*del*tmp1%MOD*tmp2%MOD;
            ans = (ans + del)%MOD;
        }
    printf("%d\n", ans);
}

@details@

感觉整场比赛都非常好。
前面的题虽然不难,但如果想得不清楚,很容易写出长代码,然而其实有优美的解法存在。

上一篇:Domino 怎样整Hibernate最佳实践


下一篇:LeetCode——475.供暖器