数据结构第五次实验——并查集、树在线评测

圆的划分
【问题描述】
在一个平面上画了n 个圆。圆心坐标和半径均为整数。现我们定义满足如下性质的图形为一个块:
 每一个圆都是一个块;
 如果两个块有公共的部分(包括相切),那么这两个块就会形成一个新的块,否则这两个块就是不同的。
示例:图1 中的5 个圆形成了2 个不同的块。
这里写图片描述
图2 中的4 个圆形成了1 个块。
这里写图片描述
任务:找出这些圆中不同的块的数目。
【输入】
第一行包括一个整数n,表示圆的数目。
以下的n 行,每行3 个用空格隔开的数x,y,r。(x,y)是圆心坐标,r 是半径。所有的坐标及半径都是不大于30000 的非负整数。
【输出】
这些圆所形成的不同的块的数目。
【输入输出样例】

circle.in circle.out
3
0 0 2
2 2 2
2 0 1
1

【数据范围限制】
100%的数据满足:n<=7000。

在图的遍历OJ作业中曾经提过,并查集是一种解决连通性问题很好的一种工具,这道题就是典型的连通性问题。只要两个圆不是相离的,就属于同一个块,就属于同一个连通分量。当然这个题也可以用bfs和dfs,不过需要优化,对于这个题讲,bfs和dfs的时间复杂度都是n方的,而n最大是7000,再加上乘法运算,是可能超时的,应该可以首先预处理出每个结点是否相连,bfs中查询相连就是O(1)的了,这样应该可以卡过去。

还有一个细节问题就是判断是否两圆外离时两个半径的和的平方会爆int,当时没有想到也过了,感谢zz大佬。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int MAXN = 7005;
int father[MAXN];
int n,x[MAXN],y[MAXN],r[MAXN];

int find(int x)
{
    while (x != father[x]) {
        father[x] = father[father[x]];
        x = father[x];
    }
    return x;
}

void merge(int x, int y)
{
    x = find(x);
    y = find(y);
    if (x != y) {
        father[x] = y;
    }
    return ;
}

inline bool ok(int i, int j)
{
    return !((x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]) > 1LL * (r[i] + r[j]) * (r[i] + r[j]));
}

int main()
{
    freopen("circle.in","r",stdin);
    freopen("circle.out","w",stdout);

    scanf("%d",&n);
    for (int i = 1; i <= n; i ++) {
        scanf("%d%d%d",&x[i],&y[i],&r[i]);
    }
    for (int i = 1; i <= n; i ++) {
        father[i] = i;
    }
    for (int i = 1; i <= n; i ++) {
        for (int j = i + 1; j <= n; j ++) {
            if (ok(i, j)) {
                merge(i, j);
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i ++) {
        if (father[i] == i) {
            ++ ans;
        }
    }
    printf("%d\n",ans);

    return 0;
}

二叉树问题
【问题描述 】
如下图所示的一棵二叉树的深度、宽度及结点间距离分别为:
深度:4 宽度:4(同一层最多结点个数)
结点间距离: ⑧→⑥为8  (3×2+2=8)
⑥→⑦为3  (1×2+1=3)
注:结点间距离的定义:由结点向根方向(上行方向)时的边数×2,
与由根向叶结点方向(下行方向)时的边数之和。
这里写图片描述

【输 入 】
输入文件第一行为一个整数n(1≤n≤100),表示二叉树结点个数。接下来的n-1行,表示从结点x到结点y(约定根结点为1),最后一行两个整数u、v,表示求从结点u到结点v的距离。

【输 出 】
三个数,每个数占一行,依次表示给定二叉树的深度、宽度及结点u到结点v间距离。

【样  例 】

输入:

10
1 2
1 3
2 4
2 5
3 6
3 7
5 8
5 9
6 10
8 6
输出:
4
4
8

一开始读错题了,以为只要求输出u和v间的加权距离,只有100个点,于是把树当成了图,正向的边权为1,反向的边权为2,暴力地跑一遍spfa就可以了,写完了才发现还需要求树的深度和宽度,于是就在原来的上面加了一个层序遍历,dep数组记录每个结点所在的层数,层序遍历过程中如果当前结点的dep值与上一个出队结点相同,说明他们在同一层,否则就是新的一层的开始,记录同一层数的结点数的最大值就是宽度了。

边比较少,所以就用链式前向星存图。这个题结点比较少,所以姿势就很多,老师讲解的时候说father链也可以,确实暴力啊,要是点多了还是老老实实LCA吧。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int MAX = 105;
const int INF = 0x3f3f3f3f;
struct Edge {
    int to,w,nt;
};

Edge e[MAX * 2];
int n,head[MAX], cnt, dis[MAX], vis[MAX];
int dep[MAX], maxdep, maxwid;

void init(void)
{
    cnt = 1;
    maxdep = 0;
    maxwid = 1;
    memset(head, 0, sizeof(head));
    memset(dis, 0x3f, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    memset(dep, 0, sizeof(dep));
    return ;
}

void add(int u, int v, int w)
{
    e[cnt].to = v;
    e[cnt].w = w;
    e[cnt].nt = head[u];
    head[u] = cnt;
    ++ cnt;
    return ;
}

void spfa(int u)
{
    dis[u] = 0;
    vis[u] = 1;
    queue<int> q;
    q.push(u);
    while (!q.empty()) {
        int now = q.front(); q.pop(); 
        for (int i = head[now]; i; i = e[i].nt) {
            if (dis[e[i].to] > dis[now] + e[i].w) {
                dis[e[i].to] = dis[now] + e[i].w;
                if (!vis[e[i].to]) {
                    vis[e[i].to] = 1;
                    q.push(e[i].to);
                }
            }
        }
        vis[now] = 0;
    }
    return ;
}

void levelT(int root)
{
    queue<int> q;
    q.push(root);
    int widnow = 1;
    int pre = root;
    while (!q.empty()) {
        int now = q.front(); q.pop();
        for (int i = head[now]; i; i = e[i].nt) {
            if (e[i].w == 1) {
                q.push(e[i].to);
                dep[e[i].to] = dep[now] + 1;
                if (dep[e[i].to] > maxdep) {
                    maxdep = dep[e[i].to];
                }
            }
        }
        if (dep[now] == dep[pre]) {
            ++ widnow;
            if (widnow > maxwid) {
                maxwid = widnow;
            }
        } else {
            widnow = 1;
        }
        pre = now;
    }
    return ;
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    init();
    int u,v;
    scanf("%d",&n);
    for (int i = 1; i <= n - 1; i ++) {
        scanf("%d%d",&u,&v);
        add(u,v,1);
        add(v,u,2);
    }
    scanf("%d%d",&u,&v);

    spfa(u);
    levelT(1);
    printf("%d\n%d\n",maxdep + 1, maxwid);
    printf("%d\n",dis[v]);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值