cf1511 E. Colorings and Dominoes
题意:
给定 n×m 格子,每格为黑色或白色。
你可以给每个白色格子染成红色或蓝色,那么显然有 \(2^{tot}\) 种染色方案(\(tot\) 为白色格子数量)
对于每一种染色方案,你要尽可能往上放大小为 1×2 的骨牌,规则是:
- 横着的骨牌必须在两个红色格子上
- 竖着的骨牌必须在两个蓝色格子上
现在想要计算每种涂色方案的最多骨牌摆放数量之和取模。
思路:
一开始我考虑计算每个骨牌的贡献,发现会重复计算,然后不知道怎么办。
(其中一个)正解是用概率和期望。
注意到每行每列的每段连续白格是独立的,也就是说我们只需要处理若干长度不同的 1×len 段。
每段的算法是一样的,首先计算横放的骨牌。\(f_n\) 表示 n 个格子,最后两个格子一定要放一个骨牌的概率。
则一种涂色方案中骨牌数的期望为 \(\sum p_{len}\),答案就乘上方案数,即 \(2^{tot}\times \sum p_{len}\)
\(p_0=p_1=0\);
\(p_2=\frac 14\) 即两个都是红色;
\(p_3=\) 最后两个是红色,但减去三个都是红色的情况(否则不一定要放在最后两格,有可能放在前两格)\(=\frac 14 -\frac 18\);
\(p_4=\frac 14 -\frac 18 + \frac 1{16}\),因为四个都是红色时,最后两格也一定要放;
以此类推。
(实际上要求最后两个是红色,前面有偶数个红色)
线性递推预处理这个交错级数即可。
竖放的骨牌也是一样的。
void sol() {
int n, m, tot = 0; cin >> n >> m;
vector<string> g(n);
for(string &s : g) {
cin >> s;
for(char c : s) tot += c == 'o';
}
vector<ll> p(N, 0); //递推p_i
for(int i = 2; i < N; i++)
p[i] = add(p[i-1], (i%2?-1:1) * inv(qmi(2, i)));
ll ans = 0;
for(int i = 0; i < n; i++) //横着的
for(int j = 0, len = 0; j < m; j++) {
if(g[i][j] == '*') len = 0; else len++;
ans = add(ans, p[len]);
}
for(int j = 0; j < m; j++) //竖着的
for(int i = 0, len = 0; i < n; i++) {
if(g[i][j] == '*') len = 0; else len++;
ans = add(ans, p[len]);
}
cout << ans * qmi(2, tot) % mod;
}

浙公网安备 33010602011771号