zoukankan      html  css  js  c++  java
  • POJ 2588 并查集判联通

    /**
        这一做用了差不多5个小时,又是一道手机提交AC 的题
        此题主要思想:建图和检查连通性,这里用并查集实现
    
        题意:穿越1000*1000的正方形田野,要求从田野左边界进入从右边界出来,田野里面有
        n条蛇,每条蛇都有自己的以(x,y)为圆心r为半径的领地,如果他一旦跃入蛇的领地内(注意是 < r),
        将会被咬,问他能否安全穿越这片田野。
    
        解析:初看一筹莫展,无从下手,躺下想了几分钟后有了大概思路,接着实现,调试
        1、首先放开其它条件只考虑是否能顺利通过,怎样才能顺利通过
        2、脑子里面是否有条从上边界到下边界的曲线,如果这条曲线是封闭的,他就不能过来
        3、怎样构造这条曲线?连接圆心。
        4、连接哪两个圆圆心?能够相交的圆圆心。
        5、把上边界下边界看成两个点标号为0和n+1,如有圆与其相交则连接圆心与其连接。
        6、现在已经大概能呈现一幅图的画面了。图中的点为边界和圆心构成
        例:输入数据 2
                     400 400 500
                     600 600 500
            把两个圆心标号为1、2,根据上述条件可建立如下图
            <0,1> <1,2> <2,3> 图中有 0,1,2,3 四个点 3条边
        可以看到从边界0到3是联通的,所以他不能顺利通过
    
        如下代码具体实现
    */
    #include <math.h>
    #include <stdio.h>
    #include <string.h>
    #define sqr(x) ((x)*(x))
    #include <algorithm>
    
    using namespace std;
    
    const int M = 1005;
    
    struct point {
        double x, y, r;
    }p[M];          // 圆心的数据结构
    
    struct lr {
        double u, d;
        int i;
    }l[M], r[M];    // 实现从边界最北边的点进入田野,暂且不要考虑
    
    bool map[M][M]; // map[1][2] == true,则1、2两点存在边<1,2>
    int path[M];    // 并查集的数据结构和kruskal算法中的并查一样
    
    ///////////////////////////////////////////
    // 并查集得函数,并查集只是实现这道题的工具,
    // 其实好多题不是专门考会不会用工具,而掌握
    // 这些工具是做题必不可少的技能,剩下的工作
    // 才是动脑子来 AC
    int find(int x) {
        if (x != path[x])
            path[x] = find(path[x]);
        return path[x];
    }
    
    // 这也是实现从最北边进入田野的函数,暂且不考虑
    bool jud(lr tt[], double x, int n) {
        for (int i=0; i<n; i++)
            if (tt[i].u > x && tt[i].d < x)
                return false;
        return true;
    }
    
    int main() {
    
        int n;
    
        while (scanf("%d", &n)!=EOF)
        {
    		memset(map, 0, sizeof (map));
    
    		int ll=0, rr=0;
    		// 下面循环用于输入圆心坐标和建立与上下左右边界关系
    		for (int i=1; i<=n; i++) {
    			scanf("%lf%lf%lf", &p[i].x, &p[i].y, &p[i].r);
    			if (p[i].y+p[i].r > 1000)   // 与上边界的关系
    				map[0][i] = map[i][0] = true;
    			if (p[i].y-p[i].r < 0)      // 与下边界的关系
    				map[n+1][i] = map[i][n+1] = true;
                // 判断是否能顺利通过只考虑与上下边界关系即可
    
                // 下面建立左右边界是为了完成题目输出要求,先不必考虑
    			if (p[i].x-p[i].r < 0) {    // 与左边界的关系
                    // l[ll].u, l[ll].d,l[0].i = i, 表示第ll个点,
                    // 圆心为i,在左边界上相交区间的为(l[ll].d, l[ll].u)
    				l[ll].u = p[i].y + sqrt(sqr(p[i].r)-sqr(p[i].x));
    				l[ll].d = p[i].y - sqrt(sqr(p[i].r)-sqr(p[i].x));
    				l[ll++].i = i;
    
    			}
    			if (p[i].x+p[i].r > 1000) {
    				r[rr].u = p[i].y + sqrt(sqr(p[i].r)-sqr(1000-p[i].x));
    				r[rr].d = p[i].y - sqrt(sqr(p[i].r)-sqr(1000-p[i].x));
    				r[rr++].i = i;
    			}
    		}
    		// 建立各个圆心之间的关系,上下边界与圆心关系在上面循环中已经建好
    		for (int i=1; i<n; i++)
    			for (int j=i+1; j<=n; j++) {
    				if (p[i].r+p[j].r > sqrt(sqr(p[i].x-p[j].x)+sqr(p[i].y-p[j].y))) {
    					map[i][j] = map[j][i] = true;
    				}
    			}
            // 到目前为止上面建图中除了不必考虑的部分,还是非常好理解的
    
            // 下面是实现并查集的初始化
    		for (int i=0; i<=n+1; i++)
    			path[i] = i;
            ///////////////////////////////////////////////
            // 经过下面的对每个有联系的点进行并查集处理后,
            // 如想判断两个点a,b是否属于一个集合只需
            // if (find(a) == find(b))
            //      则a,b在同一个集合,即a和b联通
            ///////////////////////////////////////////////
    		for (int i=0; i<=n; i++)
    			for (int j=i+1; j<=n+1; j++) {
    				if (map[i][j]) {
    					int x = find(i);
    					int y = find(j);
    					if (x != y)
    						path[y] = x;
    				}
    			}
    
    		if (find(n+1) == find(0))   // 如果上边界和下边界在一个集合,则联通
    			printf("Bill will be bitten.\n");
            /////////////////////////////////////////////////
            // 实现上面只用了半个小时,下面的选取
            // 最高点着实错了有7、8次,最后用最简单的思想实现了
            // 但还是感觉非常冗余,反正是过了,实在不想再想了
            ////////////////////////////////////////////////
    		else {
    			double lflag = -1, rflag = -1;
    			// lflag 记录左边界可进入的最高点, rflag记录可从右边界出来的最高点
    			double lu=1000, ld=0, ru=1000, rd=0;
                // lu表示左边界可用区间的最高点,ld左边界可用区间的最低点
    
                /////////////////////////////////////////////////////
                // 下面的循环找出可用区间(ld, lu), 因为如果find(a)==find(0)
                // 圆a 又与左边界相交,相交部分到最高点1000,都是不可用的,
    			for (int i=0; i<ll; i++) {
    				if (find(l[i].i)==find(0) && l[i].d < lu)
    					lu = l[i].d;
    				if (find(l[i].i)==find(n+1) && l[i].u > ld)
    					ld = l[i].u;
    			}
    			/////////////////////////////////////////////////////////
    			// 判断最高点 1000 是否可用,如果在圆和左边界相交区域则不可用
    			// 例如:l[ll].u == 1200 l[ll].d == 800, 1000位于(800,1200)区间
    			if (jud(l,1000,ll) && lu==1000)
    				lflag = 1000;
                /////////////////////////////////////////////
                // 最高点一定坐落在左边界的最高点或与圆的交点
                // 所以判断交点是否在可用区间,判断交点是否可用,判断lflag 是否值得替换
    			for (int i=0; i<ll; i++) {
    				if (l[i].u<=lu && l[i].u>=ld && jud(l,l[i].u,ll) && lflag < l[i].u) {
    					lflag = l[i].u;
    				}
    				if (l[i].d<=lu && l[i].d>=ld && jud(l,l[i].d,ll) && lflag < l[i].d) {
    					lflag = l[i].d;
    				}
    			}
    
                //下面是右边界处理如左边界
    			for (int i=0; i<rr; i++) {
    				if (find(r[i].i)==find(0) && r[i].d < ru)
    					ru = r[i].d;
    				if (find(r[i].i)==find(n+1) && r[i].u > rd)
    					rd = l[i].u;
    			}
    			if (jud(r,1000,rr) && ru==1000)
    				rflag = 1000;
    			for (int i=0; i<rr; i++) {
    				if (r[i].u<=ru && r[i].u>=rd && jud(r,r[i].u,rr) && rflag < r[i].u) {
    					rflag = r[i].u;
    				}
    				if (r[i].d<=ru && r[i].d>=rd && jud(r,r[i].d,rr) && rflag < r[i].d) {
    					rflag = r[i].d;
    				}
    			}
    
    			if (ll == 0)
    				lflag = 1000;
    			if (rr == 0)
    				rflag = 1000;
    			if (rflag<0 || lflag<0)
    				printf("Bill will be bitten.\n");
    			else
    				printf("Bill enters at (0.00, %.2lf) and leaves at (1000.00, %.2lf).\n", lflag, rflag);
    		}
        }
        return 0;
    }
    


  • 相关阅读:
    elasticsearch数据迁移
    Leetcode <剑指 Offer 64. 求1+2+…+n>
    Leetcode <27.移除元素>
    Leetcode <7.整数反转>
    Leetcode <1. 两数之和>
    python实现对于告警规则的判断思路
    Python实现密码生成器
    Autojs 打包APP+签名
    Docker 文档整理
    Pycharm 连接Linux远程开发
  • 原文地址:https://www.cnblogs.com/zcube/p/4194574.html
Copyright © 2011-2022 走看看