Week10图论

文章包含了五道编程竞赛题目,主要涉及图论算法的应用,如寻找欧拉路径解决最少笔画问题,深度优先搜索确定合根植物数量,弗洛伊德算法求解最短路径,以及判断猴子能否通过跳跃在树间移动的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一题:

# Einstein学画画

## 题目描述

Einstein 学起了画画。

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

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

## 输入格式

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

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

一条边不会被描述多次。

## 输出格式

一个数,即问题的答案。

## 样例 #1

### 样例输入 #1

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

### 样例输出 #1

```
1
```

## 提示

对于 $50 \%$ 的数据,$n \le 50$,$m \le 100$。

对于 $100\%$ 的数据,$1 \le n \le 1000$,$1 \le m \le {10}^5$。

思路:

这是是欧拉路板题,甚至还不需要求路径。 

无向图找欧拉路,

根据定理:

若恰通过图中每条边一次回到起点,则称该回路为欧拉(Euler)回路。
具有欧拉回路的图称为欧拉图。
定理1:
一个无向图是欧拉图,当且仅当该图所有顶点度数都是偶数。
一个有向图是欧拉图,当且仅当该图所有顶点度数都是0(入度与出度之和)。
定理2:
存在欧拉回路的条件:图是连通的,且不存在奇点(顶点度数为奇数)

只需要找出该图的所有奇点数目

并将数目除以2

ACcode:

#include<bits/stdc++.h>
using namespace std;
int a[2010];
int ans=0;
int main()
{
    int i,j,k,m,n,x,y;
    scanf("%d%d",&m,&n);
    for(i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        a[x]++;
        a[y]++;
    }
    for(i=1;i<=m;i++)
    {
        if(a[i]%2==1)
        {
            ans++;
        }
    }
    if(ans)
    printf("%d\n",ans/2);
    else 
    printf("1\n");
    return 0;
}
 

第二题:

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

## 题目描述

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

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

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

## 输入格式

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

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

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

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

比如:$5 \times 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
```

## 提示

## 样例解释

![](https://cdn.luogu.com.cn/upload/image_hosting/9q0xulxh.png)

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

 

思路:

dfs求连通块 

在dfs时使用

记录二维数组去记录

每次调用dfs函数,ans加1

遍历全部的方块

即可找到ans

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int maxx=1e6+100;
int f[maxx];
int n,m,nn;

inline void init()
{
	for(int i=0;i<=n*m;i++) f[i]=i;
}
inline int getf(int u)
{
	return u==f[u]?u:f[u]=getf(f[u]);
}
inline void merge(int u,int v)
{
	int t1=getf(u);
	int t2=getf(v);
	if(t1!=t2)
	{
		f[t1]=t2;
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	init();
	scanf("%d",&nn);
	int x,y;
	for(int i=1;i<=nn;i++)
	{
		scanf("%d%d",&x,&y);
		merge(x,y);
	}
	int ans=0;
	for(int i=1;i<=n*m;i++) if(getf(i)==i) ans++;
	cout<<ans<<endl;
	return 0;
}

第三题:

# [NOIP2017 提高组] 奶酪

## 题目背景

NOIP2017 提高组 D2T1

## 题目描述

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

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

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

空间内两点 $P_1(x_1,y_1,z_1)$、$P2(x_2,y_2,z_2)$ 的距离公式如下:


$$\mathrm{dist}(P_1,P_2)=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2}$$

## 输入格式

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

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

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

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

## 输出格式

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

## 样例 #1

### 样例输入 #1

```

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$ 说明】

 ![](https://cdn.luogu.com.cn/upload/pic/10860.png) 

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

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

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

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

输出 `Yes`。

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

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

输出 `No`。

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

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

输出 `Yes`。

【数据规模与约定】

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

对于 $40\%$ 的数据,$1 \le n \le 8$,$1 \le h$,$r \le 10^4$,坐标的绝对值不超过 $10^4$。

对于 $80\%$ 的数据,$1 \le n \le 10^3$,$1 \le h , r \le 10^4$,坐标的绝对值不超过 $10^4$。

对于 $100\%$ 的数据,$1 \le n \le 1\times 10^3$,$1 \le h , r \le 10^9$,$T \le 20$,坐标的绝对值不超过 $10^9$。

思路:

先把所有能够到达奶酪底部的点都加入队列

每次从队列中弹出一个,

判断他的z坐标加上r后是否能到达顶部,

可以的话直接退出循环

否则判断它和所有的点是否能够连通,

如果可以连通就加入队列

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
ll T,n,h,r,d;
ll x[maxn],y[maxn],z[maxn];
bool vis[maxn],ok;
inline ll read(){
	char c=getchar();
	ll s=0,st=1;
	while (c<'0'||c>'9') {
		if (c=='-') st=-1;
		c=getchar();
	}
	while (c>='0'&&c<='9') {
		s=(s<<3)+(s<<1)+c-'0';
		c=getchar();
	}
	return s*st;
} 
ll ab(ll x){return x>0?x:-x;}//绝对值 
ll sq(ll x){return x*x;}//平方 
int main(){
	T=read();
	while (T--){
	n=read();h=read();r=read();
	d=r*2;
	for (rint i=1;i<=n;++i){
		x[i]=read();
		y[i]=read();
		z[i]=read();
	}
	queue <int> q;
	for (rint i=1;i<=n;++i){
		if (z[i]+r>=0&&z[i]-r<=0) {
			vis[i]=1;
			q.push(i);
		}
	}
	ll dis,dx,dy,dz;
	while (!q.empty()){
		int k=q.front();q.pop();
		if (r+z[k]>=h) {
			ok=1;break;
		}
		for (rint i=1;i<=n;++i){
			if (vis[i]) continue;
			dx=ab(x[k]-x[i]);
			if (dx>d) continue;
			
			dy=ab(y[k]-y[i]);
			if (dy>d) continue;
			dz=ab(z[k]-z[i]);
			if (dz>d) continue;
			dis=sq(dx)+sq(dy)+sq(dz);
			if (dis<=sq(d)) {
				vis[i]=1;
				q.push(i);
			}
		}
	}
	if (ok)	printf("Yes\n"),ok=0;
	else printf("No\n");
	for (rint i=1;i<=n;++i) vis[i]=0;
	}
	return 0;
}

 第四题:

# 灾后重建

## 题目背景

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

## 题目描述

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

## 输入格式

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

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

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

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

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

## 输出格式

共$Q$行,对每一个询问$(x, y, t)$输出对应的答案,即在第$t$天,从村庄$x$到村庄$y$的最短路径长度为多少。如果在第t天无法找到从$x$村庄到$y$村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄$y$在第$t$天仍未修复完成,则输出$-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\%$的数据,有$N≤50$;

对于$30\%$的数据,有$t_i= 0$,其中有$20\%$的数据有$t_i = 0$且$N>50$;

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

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

 用Floyd解决

我们可以根据每个村庄重建的时间作为上面的k来更新最短路

假如当前村庄没有重建,那么一定是-1

否则判断是否联通

 

#include<bits/stdc++.h>
using namespace std;
#define R register
#define N 505
#define inf 707406378
inline void in(int &x) {
    static int ch; static bool flag;
    for(flag = false,ch = getchar();ch < '0'||ch > '9';ch = getchar()) flag |= ch == '-';
    for(x = 0;isdigit(ch);ch = getchar()) x = (x<<1) + (x<<3) + ch - '0';
    x = flag ? -x : x;
}
inline void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
int n,m,p; 
int Ti[N];
int dis[N][N];
inline void up(int k){
    for(R int i=0;i<n;++i)
        for(R int j=0;j<n;++j)
            dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
}
inline int dy(){
    in(n),in(m);
    for(R int i=0;i<n;++i)in(Ti[i]);
    for(R int i=0;i<n;++i){
        for(R int j=0;j<n;++j)dis[i][j]=inf;
        dis[i][i]=0;
    }
    for(R int i=1;i<=m;++i){
        R int a,b,c;in(a),in(b),in(c);
        dis[a][b]=dis[b][a]=c;
    }
    in(m);
    while(m--){
        R int x,y,t;in(x),in(y),in(t);
        while(Ti[p]<=t && p<n)up(p),p++;
        if(Ti[x]>t || Ti[y]>t || dis[x][y]>=inf)write(-1);
        else write(dis[x][y]);
        putchar('\n');
    }
    exit(0);
}
int QAQ = dy();
int main(){;}

 第五题:

# [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 修正数据

生成树 

把两个点之间的距离算出来

记录下最大的边,

和每只猴子的跳跃距离比较,

如果跳跃距离大就ans++。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
template <class T> void read (T &x)
{
    int f=0; x=0; char ch=getchar();
    while(ch<'0'||ch>'9')  
    {
        f|=(ch=='-');
        ch=getchar();
    }
    while(ch>='0' && ch<='9')
    {
        f=(x>>1)+(x>>3)+(ch^48);
        ch=getchar();
    }
    x=f?-x:x;
    return;
}
int father[2001],x[2001],y[2001];
double jump[2001];
int m,n;
int find(int x)
{
    if(father[x]==x)  return x;
    else {
        int ff=find(father[x]);
        return father[x]=ff;
    }
}

int main(){
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%lf",&jump[i]);
    
    }
    scanf("%d",&n);
    sort(jump+1,jump+1+m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&x[i],&y[i]);
    }
    int ans=0;
    for(int i=1;i<=m;i++)
    {
        for(int q=1;q<=n;q++)
           {
             father[q]=q;
         }
        int sum=0;
        for(int j=1;j<n;j++)
        {
            for(int k=j+1;k<=n;k++)
            {
                int temp1=find(j);
                int temp2=find(k);
                if(temp1==temp2)  continue;
                double lenth=sqrt((x[j]-x[k])*(x[j]-x[k])+(y[j]-y[k])*(y[j]-y[k]));
                if(lenth<=jump[i] )
                {
                    father[temp1]=temp2;
                    sum++;
                    if(sum==n-1)  break;
                }
                if(sum==n-1)  break;
            }
            if(sum==n-1)   break;
        }
        if(sum==n-1)
            {
             ans=m-i+1;
            break;
        }
            
    }
    printf("%d\n",ans);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值