CSU2015 Artwork题解(BFS+并查集)

原题见:题目传送门

题意:给定一个n*m(1<=n,m<=1000)的矩阵白块,然后输入q(1<=q<=1e4)对结点,每对结点在同一行或者同一列(两个结点连成的线段上的结点也被连接起来了),涂黑这些结点对连线上的所有白色结点(包括两端点)。每次涂黑后都要求输出此时矩阵中白色联通块的数量

(额。。。感觉总结能力完全不行,还不如直接看题。。。。)

输入:

第一行:n m q

往下q行:x1 y1 x2 y2

(x1,y1)与(x2,y2)就是即将涂黑的俩端点,输入保证x1==x2或者y1==y2

输出:q行,每次涂黑后的矩阵中的白色联通块数量

时间限制:4s

简单分析一波:常规思维是输入一组,便求一次白色联通块数量,然而,随便算一下复杂度吧,DFS或者BFS求联通块,复杂度差不多是n*m,然后q次查询,也就是O(n*m*q),大概是1e3*1e3*1e4=1e10。4s?不存在的~然后,不擅长做图论题的我就不会了——但是我有队友啊——这货这一题做第三次了。。。

清真分析一波:既然正向思考,输入一次,计算一次不行的话,那就逆向算喽~记下每一次连接的两个端点,标记第一次被涂黑的时间戳,也就是第几次它被涂黑了~然后我们直接算最后一次有多少个白色联通块,这一步复杂度是O(n*m),然后反向一次次地涂白,看每次涂白时是否构成了新的联通块,或者涂白后是否将不同联通块连接在了一起,这一步复杂度最坏是O(m)或者O(n),二者范围一样,都是1e3,就以O(n)算,q次,也就是O(n*q),这样总的复杂度也就是O(n*m+n*q),也就1e7数量级的复杂度,完美~

然后就是代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;

struct point
{
    int x, y;
    point(int X = 0, int Y = 0):x(X), y(Y) {};
    void read()
    {
        scanf("%d%d", &x, &y);
    }
    bool operator==(const point& c) const
    {
        return x == c.x && y == c.y;
    }
};//结构体:点
struct line
{
    point x, y;
    void read()
    {
        x.read();
        y.read();
    }
}h[10010];//结构体:两个点,记录每一次涂黑的两个端点
point dad[1001][1001];//记录每一个点的父节点
point father(point p)//更新父节点
{
    if(p == dad[p.x][p.y])      return p;
    return dad[p.x][p.y] = father(dad[p.x][p.y]);
}
int t[1001][1001];      //记录每一个结点第一次被涂黑的时间戳
int cnt[10010];         //记录每一次查询的联通块数量
bool used[1001][1001];  //BFS求联通块时判断(i,j)是否被访问过
int main()
{
    #ifdef AFei
    freopen("in.txt", "r", stdin);
    #endif // AFei
    int n, m, q;
    scanf("%d%d%d", &n, &m, &q);
    memset(t, -1, sizeof t);// t[i][j]==-1表示(i,j)点是白的~
    for(int i = 0; i < q; ++ i)
    {
        h[i].read();//读入每次涂黑的线段的两个端点

        int xl = min(h[i].x.x, h[i].y.x)-1, xr = max(h[i].x.x, h[i].y.x)-1;
        int yl = min(h[i].x.y, h[i].y.y)-1, yr = max(h[i].x.y, h[i].y.y)-1;
        for(int j = xl; j <= xr; ++ j)//记录某一点在哪一次被涂黑的
            for(int k = yl; k <= yr; ++ k)
                if(t[j][k] == -1)
                    t[j][k] = i;
    }

    for(int i = 0; i < n; ++ i)
        for(int j = 0; j < m; ++ j)
        {
            dad[i][j] = point(i, j);//初始认为每一个点都是一个联通块,它们以自己为父节点
        }

    //求最终联通块数量
    cnt[q-1] = 0;
    int row[4] = {0, 0, 1, -1};
    int col[4] = {1, -1, 0, 0};

    for(int i = 0; i < n; ++ i)
    {
        for(int j = 0; j < m; ++ j)
        {
            if(t[i][j] == -1 && father(point(i, j)) == point(i, j))//BFS找联通块,整个联通块的父结点都是(i,j)
            {
                point d = point(i, j);
                cnt[q-1] ++;
                queue<point> Q;
                Q.push(d);
                while(!Q.empty())
                {
                    point next = Q.front();
                    Q.pop();
                    for(int k = 0; k < 4; ++ k)
                    {
                        int nX = next.x + row[k], nY = next.y + col[k];
                        if(nX >= 0 && nX < n && nY >= 0 && nY < m && used[nX][nY] == 0 && t[nX][nY] == -1)
                        {//在范围内的点,并且没被访问过,并且是白块的点
                            used[nX][nY] = 1;
                            Q.push(point(nX, nY));
                            dad[nX][nY] = point(i, j);
                        }
                    }
                }
            }
        }
    }

    for(int i = q-1; i; -- i)//逆向求解
    {
        cnt[i-1] = cnt[i];//cnt[i]就是第i次查询的解,i从0开始

        int xl = min(h[i].x.x, h[i].y.x)-1, xr = max(h[i].x.x, h[i].y.x)-1;
        int yl = min(h[i].x.y, h[i].y.y)-1, yr = max(h[i].x.y, h[i].y.y)-1;

        for(int j = xl; j <= xr; ++ j)
            for(int k = yl; k <= yr; ++ k)
            {
                if(t[j][k] == i)//如果(j,k)是在第i次涂黑的
                {
                    t[j][k] = -1;//强行涂白
                    point P[4] = {point(-1, -1), point(-1, -1), point(-1, -1), point(-1, -1)};
                        //记录(j,k)上下左右的点的父节点,

                    bool flag = 0;//判断四周是不是都是黑块
                    //上下左右只要有一个方向是白块,就将(j,k)与那个白块所属的联通块相连通
                    if(j && t[j-1][k] == -1)
                        flag = 1, dad[j][k] = P[0] = father(point(j-1, k));
                    if(k && t[j][k-1] == -1)
                        flag = 1, dad[j][k] = P[1] = father(point(j, k-1));
                    if(j < n-1 && t[j+1][k] == -1)
                        flag = 1, dad[j][k] = P[2] = father(point(j+1, k));
                    if(k < m-1 && t[j][k+1] == -1)
                        flag = 1, dad[j][k] = P[3] = father(point(j, k+1));

                    if(!flag)//如果四周都是黑块,那么联通块+1
                        cnt[i-1] ++;
                    else
                    {
                        for(int z = 0; z < 4; ++ z)//遍历四个方向
                        {
                            if(P[z] == point(-1, -1))//如果这个方向不是白块儿
                                continue;

                            if(!(father(P[z]) == father(point(j, k))))//两个不同的联通块由于(j,k)被涂白而相连通
                            {
                                cnt[i-1] --;//总联通块数量-1
                                dad[P[z].x][P[z].y] = father(point(j, k));//更新父节点
                            }
                        }
                    }
                }
            }
    }

    for(int i = 0; i < q; ++ i)
        printf("%d\n", cnt[i]);
    return 0;
}


在135行,if(!(father(P[z]) == father(point(j, k))))

我一开始写的是if(!(P[z] == father(point(j, k))))

然后就wa了,这bug卡了半天,不懂为什么。。。先记着,以后再回来看看吧~

猜你喜欢

转载自blog.csdn.net/q1410136042/article/details/79889929