【图论初步2.0】

文章讲述了与图论相关的问题,包括求解无向图中的奇点数量、判断连通块个数以及寻找最短路径的方法。还涉及到猴子跳跃问题,即计算猴子在不同树冠间跳跃觅食的可能性,以及Kruskal算法在构建最小生成树中的应用。
摘要由CSDN通过智能技术生成

求图中奇点的个数

Einstein学画画

题目描述

Einstein 学起了画画。

此人比较懒~~,他希望用最少的笔画画出一张画……

给定一个无向图,包含 n n n 个顶点(编号 1 ∼ n 1 \sim n 1n), m m m 条边,求最少用多少笔可以画出图中所有的边。

输入格式

第一行两个整数 n , m n, m n,m

接下来 m m m 行,每行两个数 a , b a, b a,b a ≠ b a \ne b a=b),表示 a , b a, b a,b 两点之间有一条边相连。

一条边不会被描述多次。

输出格式

一个数,即问题的答案。

样例 #1

样例输入 #1

5 5
2 3
2 4
2 5
3 4
4 5

样例输出 #1

1

提示

对于 50 % 50 \% 50% 的数据, n ≤ 50 n \le 50 n50 m ≤ 100 m \le 100 m100

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000 1 ≤ m ≤ 10 5 1 \le m \le {10}^5 1m105

#include<iostream>
using namespace std;
const int maxn=1e3+5;
int acme[maxn];//用于存储每个节点的度数
int sum;//用于存储奇点的个数
int main(){
    int n,m;
    cin>>n>>m;//n表示节点的个数,m表示边的条数
    int a,b;
    for(int i=1;i<=m;++i){
        cin>>a>>b;//表示边的两个端点
        acme[a]++;
        acme[b]++;
    }
    for(int i=1;i<=n;++i){
        if(acme[i]%2){
            sum++;//检查acme[i]是否为奇数。如果是,将sum加1,表示找到了一个奇点
        }
    }
    //检查sum的值是否为0。如果是,输出1,否则输出sum/2
    if(!sum)cout<<1;
    else cout<<sum/2;
}

求图的连通块的个数

[蓝桥杯 2017 国 C] 合根植物

题目描述

w 星球的一个种植园,被分成 m × n m \times n m×n 个小格子(东西方向 m m m 行,南北方向 n n n 列)。每个格子里种了一株合根植物。

这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。

如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?

输入格式

第一行,两个整数 m m m n n n,用空格分开,表示格子的行数、列数( 1 < m , n < 1000 1<m,n<1000 1<m,n<1000)。

接下来一行,一个整数 k k k,表示下面还有 k k k 行数据 ( 0 < k < 1 0 5 ) (0<k<10^5) (0<k<105)

接下来 k k k 行,第行两个整数 a a a b b b,表示编号为 a a a 的小格子和编号为 b b b 的小格子合根了。

格子的编号一行一行,从上到下,从左到右编号。

比如: 5 × 4 5 \times 4 5×4 的小格子,编号:

1  2  3  4
5  6  7  8
9  10 11 12
13 14 15 16
17 18 19 20

输出格式

一行一个整数,表示答案

样例 #1

样例输入 #1

5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17

样例输出 #1

5

提示

样例解释

时限 1 秒, 256M。蓝桥杯 2017 年第八届国赛

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e7 + 5;
int pre[maxn];//存储每个节点的父节点
int find(int x)//用于查找一个节点的父节点
{
    if (pre[x] == x)
        return x;
    return pre[x] = find(pre[x]);
}
void join(int x, int y)//用于将两个节点合并在一起
{
    int fx = find(x);
    int fy = find(y);

    pre[fx] = fy;

    return;
}

int main()
{
    int m, n, k, x, y;
    cin >> m >> n >> k;//m、n表示图的大小,k表示边的条数
    int z = m * n;
    for (int i = 1; i <= z; ++i)//将每个节点的父节点都设置为自己
    {
        pre[i] = i;
    }
    for (int i = 1; i <= k; ++i)//调用join函数将x和y合并在一起
    {
        cin >> x >> y;
        join(x, y);
    }
    int ans = 0;
    for (int i = 1; i <= z; ++i)
    {
        if (pre[i] == i)//检查pre[i]是否等于i,如果是,表示找到了一个连通块,ans加1
            ans++;
    }
    cout << ans;//表示连通块的个数
}

判断是否存在一条通路,使得一个物体从地面掉到底座上,另一个物体从底座掉到地面

[NOIP2017 提高组] 奶酪

题目背景

NOIP2017 提高组 D2T1

题目描述

现有一块大奶酪,它的高度为 h h h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为 z = 0 z = 0 z=0,奶酪的上表面为 z = h z = h z=h

现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry 则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。

位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑 到奶酪的上表面去?

空间内两点 P 1 ( x 1 , y 1 , z 1 ) P_1(x_1,y_1,z_1) P1(x1,y1,z1) P 2 ( x 2 , y 2 , z 2 ) P2(x_2,y_2,z_2) P2(x2,y2,z2) 的距离公式如下:

d i s t ( P 1 , P 2 ) = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 + ( z 1 − z 2 ) 2 \mathrm{dist}(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2} dist(P1,P2)=(x1x2)2+(y1y2)2+(z1z2)2

输入格式

每个输入文件包含多组数据。

第一行,包含一个正整数 T T T,代表该输入文件中所含的数据组数。

接下来是 T T T 组数据,每组数据的格式如下: 第一行包含三个正整数 n , h , r n,h,r n,h,r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。

接下来的 n n n 行,每行包含三个整数 x , y , z x,y,z x,y,z,两个数之间以一个空格分开,表示空洞球心坐标为 ( x , y , z ) (x,y,z) (x,y,z)

输出格式

T T T 行,分别对应 T T T 组数据的答案,如果在第 i i i 组数据中,Jerry 能从下表面跑到上表面,则输出 Yes,如果不能,则输出 No

样例 #1

样例输入 #1

3 
2 4 1 
0 0 1 
0 0 3 
2 5 1 
0 0 1 
0 0 4 
2 5 2 
0 0 2 
2 0 4

样例输出 #1

Yes
No
Yes

提示

【输入输出样例 1 1 1 说明】

第一组数据,由奶酪的剖面图可见:

第一个空洞在 ( 0 , 0 , 0 ) (0,0,0) (0,0,0) 与下表面相切;

第二个空洞在 ( 0 , 0 , 4 ) (0,0,4) (0,0,4) 与上表面相切;

两个空洞在 ( 0 , 0 , 2 ) (0,0,2) (0,0,2) 相切。

输出 Yes

第二组数据,由奶酪的剖面图可见:

两个空洞既不相交也不相切。

输出 No

第三组数据,由奶酪的剖面图可见:

两个空洞相交,且与上下表面相切或相交。

输出 Yes

【数据规模与约定】

对于 20 % 20\% 20% 的数据, n = 1 n = 1 n=1 1 ≤ h 1 \le h 1h r ≤ 1 0 4 r \le 10^4 r104,坐标的绝对值不超过 1 0 4 10^4 104

对于 40 % 40\% 40% 的数据, 1 ≤ n ≤ 8 1 \le n \le 8 1n8 1 ≤ h 1 \le h 1h r ≤ 1 0 4 r \le 10^4 r104,坐标的绝对值不超过 1 0 4 10^4 104

对于 80 % 80\% 80% 的数据, 1 ≤ n ≤ 1 0 3 1 \le n \le 10^3 1n103 1 ≤ h , r ≤ 1 0 4 1 \le h , r \le 10^4 1h,r104,坐标的绝对值不超过 1 0 4 10^4 104

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 × 1 0 3 1 \le n \le 1\times 10^3 1n1×103 1 ≤ h , r ≤ 1 0 9 1 \le h , r \le 10^9 1h,r109 T ≤ 20 T \le 20 T20,坐标的绝对值不超过 1 0 9 10^9 109

#include <bits/stdc++.h>
using namespace std;
int pre[1005];//用于存储每个节点的父节点
int f1[100005], f2[100005];//分别用于存储掉落到地面的物体和掉落到底座的物体的编号
struct cheese//用于存储每个物体的坐标和编号
{
    int x, y, z;
    int id;
} chee[100005];

long long n, h, r, x, y, z;
int find(int x)//用于查找一个节点的父节点
{
    if (pre[x] == x)
        return x;
    else
    {
        return pre[x] = find(pre[x]);
    }
}
void merge(int x, int y)
{
    int fx = find(x);
    int fy = find(y);
    if (fx != fy)
    {
        pre[fy] = fx;
    }
    return;
}

long long dis(cheese a, cheese b)
{
    long long di = ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y) + (a.z - b.z) * (a.z - b.z));
    return di;
}

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n >> h >> r;//n表示物体的个数,h表示底座的高度,r表示物体的半径
        int tot1 = 0;
        int tot2 = 0;
        for (int i = 1; i <= n; ++i)
        {
            pre[i] = i;
        }
        for (int i = 1; i <= n; ++i)
        {
            chee[i].id = i;//读入了物体的坐标和编号
            cin >> chee[i].x >> chee[i].y >> chee[i].z;
            if (chee[i].z + r >= h)//查物体是否掉落到地面或底座。如果是,将物体的编号加入对应的数组f1或f2中
            {
                ++tot1;
                f1[tot1] = i;
            }
            if (chee[i].z - r <= 0)
            {
                ++tot2;
                f2[tot2] = i;
            }
            for (int k = 1; k <= i; ++k)
            {
                if ((chee[i].x - chee[k].x) * (chee[i].x - chee[k].x) + (chee[i].y - chee[k].y) * (chee[i].y - chee[k].y) > 4 * r * r)
                    continue;
                if (dis(chee[i], chee[k]) <= 4 * r * r)//两个物体合并在一起
                {
                    int a1 = find(i);
                    int a2 = find(k);
                    if (a1 != a2)
                        pre[a1] = a2;
                }
            }
        }
        int s = 0;
        for (int i = 1; i <= tot1; ++i)//检查f1数组中的每个物体是否与f2数组中的任意一个物体在同一个连通块中
        {
            for (int k = 1; k <= tot2; ++k)
            {
                if (find(f1[i]) == find(f2[k]))
                {
                    s = 1;
                    break;
                }
            }
            if (s)
                break;
        }
        if (s)
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
}

灾后重建

题目背景

B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。

题目描述

给出 B 地区的村庄数 N N N,村庄编号从 0 0 0 N − 1 N-1 N1,和所有 M M M 条公路的长度,公路是双向的。并给出第 i i i 个村庄重建完成的时间 t i t_i ti,你可以认为是同时开始重建并在第 t i t_i ti 天重建完成,并且在当天即可通车。若 t i t_i ti 0 0 0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 Q Q Q 个询问 ( x , y , t ) (x,y,t) (x,y,t),对于每个询问你要回答在第 t t t 天,从村庄 x x x 到村庄 y y y 的最短路径长度为多少。如果无法找到从 x x x 村庄到 y y y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x x x 或村庄 y y y 在第 t t t 天仍未重建完成,则需要返回 -1

输入格式

第一行包含两个正整数 N , M N,M N,M,表示了村庄的数目与公路的数量。

第二行包含 N N N个非负整数 t 0 , t 1 , … , t N − 1 t_0, t_1,…, t_{N-1} t0,t1,,tN1,表示了每个村庄重建完成的时间,数据保证了 t 0 ≤ t 1 ≤ … ≤ t N − 1 t_0 ≤ t_1 ≤ … ≤ t_{N-1} t0t1tN1

接下来 M M M行,每行 3 3 3个非负整数 i , j , w i, j, w i,j,w w w w为不超过 10000 10000 10000的正整数,表示了有一条连接村庄 i i i与村庄 j j j的道路,长度为 w w w,保证 i ≠ j i≠j i=j,且对于任意一对村庄只会存在一条道路。

接下来一行也就是 M + 3 M+3 M+3行包含一个正整数 Q Q Q,表示 Q Q Q个询问。

接下来 Q Q Q行,每行 3 3 3个非负整数 x , y , t x, y, t x,y,t,询问在第 t t t天,从村庄 x x x到村庄 y y y的最短路径长度为多少,数据保证了 t t t是不下降的。

输出格式

Q Q Q行,对每一个询问 ( x , y , t ) (x, y, t) (x,y,t)输出对应的答案,即在第 t t t天,从村庄 x x x到村庄 y y y的最短路径长度为多少。如果在第t天无法找到从 x x x村庄到 y y y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄 y y y在第 t t t天仍未修复完成,则输出 − 1 -1 1

样例 #1

样例输入 #1

4 5
1 2 3 4
0 2 1
2 3 1
3 1 2
2 1 4
0 3 5
4
2 0 2
0 1 2
0 1 3
0 1 4

样例输出 #1

-1
-1
5
4

提示

对于 30 % 30\% 30%的数据,有 N ≤ 50 N≤50 N50

对于 30 % 30\% 30%的数据,有 t i = 0 t_i= 0 ti=0,其中有 20 % 20\% 20%的数据有 t i = 0 t_i = 0 ti=0 N > 50 N>50 N>50

对于 50 % 50\% 50%的数据,有 Q ≤ 100 Q≤100 Q100

对于 100 % 100\% 100%的数据,有 N ≤ 200 N≤200 N200 M ≤ N × ( N − 1 ) / 2 M≤N \times (N-1)/2 MN×(N1)/2 Q ≤ 50000 Q≤50000 Q50000,所有输入数据涉及整数均不超过 100000 100000 100000

#include <bits/stdc++.h>
using namespace std;
const int N = 204, INF = 0x3f3f3f3f;
int n, m;
int a[N];
int f[N][N];
void updata(int k)
{
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            if (f[i][j] > f[i][k] + f[k][j])
                f[i][j] = f[i][k] + f[k][j];
    return;
}
int main()
{
    cin >> n >> m;//#include <bits/stdc++.h>
using namespace std;
const int N = 204, INF = 0x3f3f3f3f;
int n, m;
int a[N];
int f[N][N];
void updata(int k)
{
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            if (f[i][j] > f[i][k] + f[k][j])
                f[i][j] = f[i][k] + f[k][j];
    return;
}
int main()
{
    cin >> n >> m;//n表示点的个数,m表示边的条数
    for (int i = 0; i < n; ++i)
        cin >> a[i];
    for (int i = 0; i < n; ++i)//f[i][j]表示i点到j点的最短距离
        for (int j = 0; j < n; ++j)
        {
            f[i][j] = INF;//初始化为INF,表示两点之间不连通
            f[i][i] = 0;//示i点到自己的距离为0
        }
    int s1, s2, w;
    for(int i=1;i<=m;++i){
        cin>>s1>>s2>>w;
        f[s1][s2]=f[s2][s1]=w;
    }
    int Q;
    cin>>Q;
    int now=0;
    for(int i=1;i<=Q;++i){
        cin>>s1>>s2>>w;
        //使用Floyd算法求出s1点到s2点的最短路径
        while(a[now]<=w&&now<n)//检查a[now]是否小于等于w,如果是,就调用updata函数更新f数组
        {
            updata(now);
            now++;//调用updata函数后,程序将now变量加1,再次进入循环,直到a[now]大于w为止
        }
        //检查s1点和s2点的权值是否大于w,或者s1点到s2点的距离是否为INF,如果是,就输出-1,否则输出s1点到s2点的最短距离
        if(a[s1]>w||a[s2]>w||f[s1][s2]==INF)cout<<-1<<endl;
        else cout<<f[s1][s2]<<endl;
    }
    return 0;
}

使用 Kruskal 算法来实现最小生成树。

[HAOI2006]聪明的猴子

题目描述

在一个热带雨林中生存着一群猴子,它们以树上的果子为生。昨天下了一场大雨,现在雨过天晴,但整个雨林的地表还是被大水淹没着,部分植物的树冠露在水面上。猴子不会游泳,但跳跃能力比较强,它们仍然可以在露出水面的不同树冠上来回穿梭,以找到喜欢吃的果实。

现在,在这个地区露出水面的有N棵树,假设每棵树本身的直径都很小,可以忽略不计。我们在这块区域上建立直角坐标系,则每一棵树的位置由其所对应的坐标表示(任意两棵树的坐标都不相同)。

在这个地区住着的猴子有M个,下雨时,它们都躲到了茂密高大的树冠中,没有被大水冲走。由于各个猴子的年龄不同、身体素质不同,它们跳跃的能力不同。有的猴子跳跃的距离比较远(当然也可以跳到较近的树上),而有些猴子跳跃的距离就比较近。这些猴子非常聪明,它们通过目测就可以准确地判断出自己能否跳到对面的树上。

【问题】现已知猴子的数量及每一个猴子的最大跳跃距离,还知道露出水面的每一棵树的坐标,你的任务是统计有多少个猴子可以在这个地区露出水面的所有树冠上觅食。

输入格式

输入文件monkey.in包括:

第1行为一个整数,表示猴子的个数M(2<=M<=500);

第2行为M个整数,依次表示猴子的最大跳跃距离(每个整数值在1–1000之间);

第3行为一个整数表示树的总棵数N(2<=N<=1000);

第4行至第N+3行为N棵树的坐标(横纵坐标均为整数,范围为:-1000–1000)。

(同一行的整数间用空格分开)

输出格式

输出文件monkey.out包括一个整数,表示可以在这个地区的所有树冠上觅食的猴子数。

样例 #1

样例输入 #1

4
 1 2 3 4
6
0 0
1 0
1 2
-1 -1
-2 0
2 2

样例输出 #1

3

提示

【数据规模】

对于40%的数据,保证有2<=N <=100,1<=M<=100

对于全部的数据,保证有2<=N <= 1000,1<=M=500

感谢@charlie003 修正数据

#include <bits/stdc++.h>
using namespace std;
int n, m, k, ans;
double sum = -1;
const int N = 1e7 + 5;
int place[N][3], monkey[N], pre[N];

struct tree
{
    int x, y;
    double p;
    /* data */
} trees[N];
int cmp(tree k, tree d)
{
    return k.p < d.p;
}
int find(int x)
{
    if (pre[x] == x)
        return x;
    return pre[x] = find(pre[x]);
}
int main()
{
    cin >> m;//表示有 m 只猴子
    for (int i = 1; i <= m; ++i)
    {
        cin >> monkey[i];//示每只猴子能跳的最大距离
    }
    cin >> n;//表示有 n 个树
    for (int i = 1; i <= n; ++i)
    {
        cin >> place[i][1] >> place[i][2];//表示每棵树的坐标
    }
    for (int i = 1; i <= n; ++i)//计算所有树之间的距离将这些距离和对应的树的编号存储在结构体数组 trees 中
    {
        for (int j = 1; j <= n; ++j)
        {
            if (i != j)
            {
                k++;
                trees[k].x = i;
                trees[k].y = j;
                trees[k].p = sqrt((place[i][1] - place[j][1]) * (place[i][1] - place[j][1]) + (place[i][2] - place[j][2]) * (place[i][2] - place[j][2]));
            }
        }
    }
    sort(trees + 1, trees + k + 1, cmp);//使用 sort 函数将树之间的距离从小到大排序
    //执行 Kruskal 算法的核心部分
    for (int i = 1; i <= n; i++)
    {
        pre[i] = i;//将所有树的编号设为它们自己的父亲节点
    }
    for (int i = 1; i <= k; ++i)
    {
        //对于每棵树,它调用 find 函数查找它的父亲节点
        int s1 = find(trees[i].x), s2 = find(trees[i].y);
        if (s1 != s2)//将其与另一棵树的父亲节点进行比较。如果它们不同,则将它们合并在一起
        {
            pre[s1] = s2;
            sum = trees[i].p;
        }
    }
    for (int i = 1; i <= n; ++i)//检查哪些猴子能够到达最小生成树中的边,并将答案记录在 ans 中
    {
        if (sum <= monkey[i])
            ans++;
    }
    cout << ans << endl;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值