zoukankan      html  css  js  c++  java
  • [洛谷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;
    }
    
  • 相关阅读:
    树莓派3 之 启动 和 系统配置
    树莓派3 之 初次使用
    Python 资源大全中文版
    乔布斯:遗失的访谈
    CSS3j背景渐变,字体颜色渐变,以及兼容IE写法
    系统设计相关
    JSON格式要求
    VUE解决空格和空行报错的问题
    css3实现悬停波浪效果
    css3实现匀速无限滚动效果
  • 原文地址:https://www.cnblogs.com/Clouder-Blog/p/12146649.html
Copyright © 2011-2022 走看看