记 $x \gets x + 1$ 为 $0$,$y \gets y + 1$ 为 $1$,则任何一条合法的路径都可以写成一个长为 $n + m - 2$ 的 $01$ 串。令 $A, B$ 分别为字典序最小的和最大的两条路径,即 $A$ 是能往下就往下走,$B$ 是能往右就往右走,那么有一个性质:所有的路径都夹在 $A$ 和 $B$ 之间,也就是说,如果一个点同时在 $A, B$ 上,那么这个点就在所有路径上。
如果一个点开始就同时在 $A, B$ 上,那么在搭配任意一个点一起删即可。否则,首先 $A$ 上的点一定要删一个,不然 $A$ 就是能走的。那么枚举删掉的 $A$ 上路径,然后得到新图的 $C$,$C$ 与 $A$ 不同的那部分 和 $B$ 对应的这部分 的交集大小即为答案。考虑怎么求出 $C$,首先一定是先往右上找到第一个没有被删的点 $(x, y)$,分 $(1, 1)$ 到 $(x, y)$ 和 $(x, y)$ 到 $(n, m)$ 来考虑。由于 $0$ 和 $1$ 的数量分别固定为 $x - 1$ 和 $y - 1$,所以倒着走时尽量先放 $1$,把 $0$ 留在前面。正着走直接贪心即可。
时间复杂度 $\mathcal{O}((n + m)^2)$。另外,如果把路径当成在每条 $x + y = k$ 的反对角线上选一个点走可能可以方便理解。
namespace Loop1st {
int n, m, b[N][N];
pii A[N], B[N], C[N];
char s[N][N];
ll sum, cnt, ans;
void Main(int tc) {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> (s[i] + 1);
for (int j = 1; j <= m; j++) b[i][j] = (s[i][j] == '.'), sum += b[i][j];
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) if (i + j > 2) b[i][j] &= (b[i - 1][j] || b[i][j - 1]);
for (int i = n; i; i--)
for (int j = m; j; j--) if (i + j < n + m) b[i][j] &= (b[i + 1][j] || b[i][j + 1]);
if (!b[1][1] || !b[n][m]) { cout << sum * (sum - 1) / 2 << '\n'; return ; }
int x = 1, y = 1;
for (int i = 1; i <= n + m - 1; i++) {
A[i] = {x, y};
if (b[x + 1][y]) x++;
else y++;
}
x = 1; y = 1;
for (int i = 1; i <= n + m - 1; i++) {
B[i] = {x, y};
if (b[x][y + 1]) y++;
else x++;
}
for (int i = 1; i <= n + m - 1; i++) if (A[i] == B[i]) cnt++;
ans = cnt * (sum - cnt) + cnt * (cnt - 1) / 2;
for (int i = 1; i <= n + m - 1; i++) if (A[i] != B[i]) {
auto [x, y] = A[i];
x--; y++; while (!b[x][y]) x--, y++;
int tx = x, ty = y, j = i, k = i;
C[i] = {x, y};
while (C[j] != A[j]) {
j--;
if (b[x][y - 1]) y--;
else x--;
C[j] = {x, y};
}
x = tx, y = ty;
while (C[k] != A[k]) {
k++;
if (b[x + 1][y]) x++;
else y++;
C[k] = {x, y};
}
for (int l = j + 1; l < k; l++) if (B[l] == C[l]) ans++;
}
cout << ans << '\n';
}
}