描述
题解
这个题的水真深,不得不说这个题的解题思路有些始料未及……
首先通过管道铺设的规则我们可以知道,管道最多只能有两个弯,而且管道口只能再非顶点边缘上,这不免有些让人感觉到复杂,但是也正是这个告诉我们这个题可以转化为求三类折线,贯穿线、折一弯儿端点在临边线、折两弯儿端点在对边线。
于是我们将问题转换为了折点的问题,根据不同折点可以构成的折法,可以求出来我们需要的解。
所以我们可以先求左右方向的情况,再求上下方向的情况,这里将 map 倒置了一下,上下方向的情况按照左右算。
这三类折线中,除了第二类外,其他两类不易重复,所以我们可以将这一类特别考虑,只在第一次左右方向查找时处理一下,由于代码的逻辑原因,这样处理后虽然第二类不重复了,但是第一类少了上下方向的贯穿线,于是在求第一次时,顺便求出纵向贯穿线即可。
具体还是仔细看代码吧,这个代码是我在参考了 rank1 的大神的代码后改写的(实际上也没改嘛子),注释十分详尽,希望可以一目了然……
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAXN = 2005;
int n, m;
char S[MAXN];
bool A[MAXN][MAXN], B[MAXN][MAXN];
bool D[MAXN][MAXN], U[MAXN][MAXN];
long long ans = 0;
void solve(bool T[][MAXN], int n, int m, int flag)
{
memset(D, 0, sizeof(D));
memset(U, 0, sizeof(U));
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
D[i][j] = D[i - 1][j] | T[i][j]; // 是否可以从 T[i - 1][j] 向下到达 T[i][j]
}
}
for (int i = n; i; i--)
{
for (int j = 1; j <= m; j++)
{
U[i][j] = U[i + 1][j] | T[i][j]; // 是否可以从 T[i + 1][j] 向上到达 T[i][j]
}
}
for (int i = 2; i < n; i++)
{
int tmp = 0;
if (!T[i][1] && flag)
{
tmp = 1;
}
D[i][1] = U[i][1] = 1;
for (int j = 2; j < m; j++)
{
if (T[i][j])
{
tmp = 0;
continue;
}
ans += ((!D[i][j]) + (!U[i][j])) * tmp; // 算 上+[右…右]+下 和 下+[右…右]+上
// 和 上+[右…右]+上 和 下+[右…右]+下
// 以及 从第一列开始 [右…右]+上 和 [右…右]+下 的折线的个数
ans += (!D[i][j] && !U[i][j - 1]) + (!U[i][j] && !D[i][j - 1]); // 算 上+右+上 和 下+右+下
tmp += (!D[i][j - 1]) + (!U[i][j - 1]); // 算 能到达第 i 行的部分数配合下一次循环使用
}
if (!T[i][m] && flag)
{
ans += tmp + (!D[i][m - 1]) + (!U[i][m - 1]); // 算 从前边任何一列开始 下+[右…右]
// 和 上+[右…右] 的情况 以及 左右两端 直达的情况
}
}
if (flag)
{
for (int j = 2; j < m; j++)
{
ans += (!D[n][j]); // 算 上下两端 直达的情况
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%s", S + 1);
for (int j = 1; j <= m; j++)
{
A[i][j] = S[j] == '#';
}
}
solve(A, n, m, 1);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
B[j][i] = A[i][j];
}
}
solve(B, m, n, 0);
printf("%lld\n", ans);
return 0;
}