ACM模版

描述


题解

这个题的水真深,不得不说这个题的解题思路有些始料未及……

首先通过管道铺设的规则我们可以知道,管道最多只能有两个弯,而且管道口只能再非顶点边缘上,这不免有些让人感觉到复杂,但是也正是这个告诉我们这个题可以转化为求三类折线,贯穿线、折一弯儿端点在临边线、折两弯儿端点在对边线。

于是我们将问题转换为了折点的问题,根据不同折点可以构成的折法,可以求出来我们需要的解。

所以我们可以先求左右方向的情况,再求上下方向的情况,这里将 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;
}