zoukankan      html  css  js  c++  java
  • @codeforces


    @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
    第一个样例初始长成这样:

    可以放置的方案数有如下几种:

    第二个样例初始长成这样:

    不可以放置更多的多米诺。

    @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
    ", ans);
    }
    

    @details@

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

  • 相关阅读:
    特性类
    WebGL中的第三个小程序(着色器)
    C#紧耦合的例子
    特性
    python两个目录匹配,粘贴图片
    Leetcode 53
    逻辑回归-1.原理
    多项式回归-4.模型正则化
    python 线程
    python 进程
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11741202.html
Copyright © 2011-2022 走看看