题意

一个 $n\times n$ 的方阵中,有些点可以放洒水器,有些点不能放。洒水器有两个种类:一个种类可以让它和它的左下角变成 $\texttt{C}$ ,一个种类可以让它和它的右上角变成 $\texttt{A}$ ,现在问放洒水器的方案数使得每个格子有且仅有一个字符。

$n\leq2000$

题解

好题!

这个方阵一定会被一条轮廓线分成两部分,那么 DP 维护这个轮廓线方案数。设 $f_{i,j,0/1}$ 表示以 $(i,j)$ 的右下角为轮廓线的最后点,最后的线时向右或向下的方案数。

代码

const int N = 2010, mod = 1e9 + 7;

inline ll Read() {
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

namespace Main {
	int n;
	char a[N][N];
	ll qpow (ll a, ll b) {
		ll ans = 1;
		for (; b; b >>= 1, a = a * a % mod)
			if (b & 1) ans = ans * a % mod;
		return ans;
	}
	ll f[N][N][2], cnt, inv2 = 5e8 + 4;
	int main () {
		n = Read();
		for (int i = 1; i <= n; i++) {
			scanf ("%s", a[i] + 1);
			for (int j = 1; j <= n; j++) cnt += a[i][j] == '.';
		}
		f[0][0][0] = f[0][0][1] = qpow(2, cnt);
		for (int i = 0; i <= n; i++) {
			for (int j = 0; j <= n; j++) {
				if (i) {
					(f[i][j][0] += f[i - 1][j][0]) %= mod;
					if (a[i][j] == '.') (f[i][j][0] += f[i - 1][j][1] * inv2 % mod) %= mod;
				}
				if (j) {
					(f[i][j][1] += f[i][j - 1][1]) %= mod;
					if (a[i][j] == '.') (f[i][j][1] += f[i][j - 1][0] * inv2 % mod) %= mod;
				}
			}
		}
		printf ("%lld\n", (f[n][n][0] + f[n][n][1]) % mod);
		return 0;
	}
}

int main () {
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	Main::main();
	return 0;
}
EOF

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。

博客信息

作者
Jayun
时间
2023-11-08 18:53:52