• [洛谷Luogu]P1141 01迷宫[联通块 并查集]


    题目链接

    大致题意

    相邻格子不同为连通,计算每个点所在的连通块大小。

    想法

    我采用了并查集的做法。
    开一个辅助数组记录连通块大小,每次合并的时候更新父亲节点的大小即可。

    一个点先与它上面的点判定,若判定连通则加入上方点所属的块中。
    再与左边的点判定,若连通则再将两个块合并。

    总体复杂度O(n3)O(n^3),其中并查集不加优化复杂度为O(n)O(n),使用路径压缩+按节点大小合并优化并查集,将并查集复杂度降低为近似O(1)O(1),总体复杂度近似为O(n2)O(n^2)

    并查集优化

    路径压缩

    每次查找的时候,将路径上的所有儿子的父亲改写为最原始的祖宗。这样下次就可以直接找到祖宗。
    实现:

    inline int find(const int &x)
    {
        return fa[x] == x ? x : fa[x] = find(fa[x]);//路径压缩
    }
    

    通俗写法:

    inline int find(const int &x)
    {
        if(fa[x] == x)//找到无父亲的节点,即为祖宗
        	return x;
        fa[x] = find(fa[x]);
        return fa[x];
    }
    

    按秩合并

    思考:
    有2个块需要合并,那么将小块接在大块的后面能够让整体复杂度更优。
    可以将每个祖宗及其所有儿子看做一棵树,那么节点到根的距离就是查找父亲需要的次数。
    将小树并入大树,可以避免树的深度过深。
    这个rnk数组保存的是树的深度的上界。
    通常写法:

    const int maxn = 10000000;
    int rnk[maxn],fa[maxn];//rnk为该节点所在子树的深度
    inline void unite(const int &x,const int &y)
    {
    	//合并x与y的块
    	int t1 = find(x),t2 = find(y);//找到各自的祖宗
    	if(t1 == t2)
    		return;
    	if(rnk[t1] == rnk[t2])
    	{
    		//此时两树深度相等,随便合并
    		fa[t1] = t2;
    		++rnk[t2];//合并后深度增加
    	}
    	else if(rnk[t1] > rnk[t2])
    		fa[t2] = t1;
    	else
    		fa[t1] = t2;
    }
    

    总结

    路径压缩和按秩合并都能将并查集复杂度降为O(logn)O(logn),而两者一起使用能够降为O(logn)O(log^*n)

    nn lognlog^*n
    (−∞, 1] 0
    (1, 2] 1
    (2, 4] 2
    (4, 16] 3
    (16, 65536] 4
    (65536, 2^65536] 5

    (数据转自链接,个人认为这篇博文讲并查集讲得相当不错。)

    代码

    因为按秩合并需要额外数组,而在此数量级下开数组的消耗可能比优化更大……
    于是借用了题目中维护的“连通块大小”,即节点个数,进行了按size合并的优化。

    #include <cstdio>
    using namespace std;
    #define getId(x, y) (((x - 1) * n) + y) //将二维的点赋予一个一维的别名
    int fa[1001000];
    int h[1001000];
    char all[2][1010];
    int n, m, i, j;
    //读入优化
    inline char nc()
    {
        static char buf[400000], *p1 = buf, *p2 = buf;
        return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 400000, stdin), p1 == p2) ? EOF : *p1++;
    }
    void read(char *s)
    {
        static char c;
        for (c = nc(); c != '1' && c != '0'; c = nc());
        for (; c == '0' || c == '1'; *++s = c, c = nc());
    }
    void read(int &r)
    {
        static char c;  r = 0;
        for (c = nc(); c > '9' || c < '0'; c = nc());
        for (; c >= '0' && c <= '9'; r = (r << 1) + (r << 3) + (c ^ 48), c = nc());
    }
    //并查集
    inline int find(const int &x)
    {
        return fa[x] == x ? x : fa[x] = find(fa[x]);//路径压缩
    }
    inline void unite(const int &a, const int &b)
    {
        int t1 = find(a), t2 = find(b);
        if (t1 == t2)
            return;
        h[t1] > h[t2] ? fa[t2] = t1, h[t1] += h[t2] : fa[t1] = t2, h[t2] += h[t1]; //类似按秩合并的做法,合并同时将儿子的大小加入父亲的大小中
    }
    //
    int main()
    {
        read(n);
        read(m);
        int n2 = n * n;
        for (int i = 1; i <= n2; ++i)
            fa[i] = i, h[i] = 1; //初始化每个点都是单独的联通块,大小为1
        
        int now = 0, pre = 1;
        //第一行特判,不需要与上一行作比较
        i = 1;
        read(all[now]);
        for (j = 2; j <= n; ++j)//j从2开始,可以略过第一个格子,最左上角的格子无需判断
            if (all[now][j] != all[now][j - 1])
                unite(getId(i, j), getId(i, j - 1));
    
        for (i = 2; i <= n; ++i)
        {
            now ^= 1;//使用滚动数组
            pre ^= 1;
            read(all[now]);
            if (all[now][1] != all[pre][1])
                unite(getId(i, 1), getId(i - 1, 1));//第一个格子无需与左边判断
            for (j = 2; j <= n; ++j)
            {
                if (all[now][j] != all[now][j - 1]) //因为是按行按列遍历,实际上只需要处理每个点的上方点和左边点的合并
                    unite(getId(i, j), getId(i, j - 1));
                if (all[now][j] != all[pre][j])
                    unite(getId(i, j), getId(i - 1, j));
            }
        }
        int x, y;
        for (i = 1; i <= m; ++i)
        {
            read(x);
            read(y);
            printf("%d
    ", h[find(getId(x,y))]);
        }
        return 0;
    }
    
  • 相关阅读:
    第二十九课 循环链表的实现
    第二十八课 再论智能指针(下)
    第二十七课 再论智能指针(上)
    第二十六课 典型问题分析(Bugfix)
    普通new和placement new的重载
    leetcode 581. Shortest Unsorted Continuous Subarray
    leetcode 605. Can Place Flowers
    leetcode 219. Contains Duplicate II
    leetcode 283. Move Zeroes
    leetcode 217. Contains Duplicate
  • 原文地址:https://www.cnblogs.com/Clouder-Blog/p/12146649.html
Copyright © 2020-2023  润新知