51Nod-1580-铺管道

来源:互联网 发布:开票软件升级 编辑:程序博客网 时间:2024/05/17 04:41

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;}