图论二次培训博客

本文介绍了图论中的克鲁斯卡尔算法和普利姆算法,强调了并查集在解决某些问题时的难点。同时,讨论了如何判断无向图是否能一笔画完所有边,以及欧拉图的概念。此外,还提到了DFS遍历在特定问题中的应用,以及弗洛伊德算法处理多源最短路径问题的策略,特别是在有时间限制的情况下如何逐步更新路径长度。
摘要由CSDN通过智能技术生成

本次主要学习的克鲁斯卡尔算法和普利姆算法,并查集比较难理解。

输入输出样例

输入 #1复制

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

输出 #1复制

1

说明/提示

对于50% 的数据,n≤50,m≤100。

对于 100% 的数据,n≤1000,m≤1E5。

本来以为是考普利姆算法,但是这题却考的欧拉图。这里给出结论,无向图,如果图中每个节点都是偶数条边,那么本图就可以一笔画完所有边,因为图中所有点都出入成对。如果有的节点有奇数条边呢?首先有奇数条边的节点肯定得成对存在,这样才能满足“出入守恒”。如果只有一对有奇数条边的点,就也只需要一笔画完因为从一个奇数点出另一个奇数点入,其次每增加一对有奇数条边的点,就得多画一笔才能画完。可以自己画画就明白了,这个不需要建图

#include<bits/stdc++.h>

using namespace std;
const int MAXN = 1E5+3;
int n, m, cnt[MAXN];	//cnt用来计数每个点的边数 
int main()
{
	cin >> n >> m; 
	//n个点,m条边 
	for(int i = 1; i <= m; ++i) 
	{
		int a, b;
		cin >> a >> b;
		cnt[a]++;
		cnt[b]++;
	}
	//每个点做遍历 
	int ans = 0;
	for(int i = 1; i <= n; ++i)
	{
		if(cnt[i] % 2 == 1)	//奇点
		{
			ans++;
		} 
	}
	if(ans == 0)
	{
		cout << 1 << endl;
	}
	else
	{
		cout << ans/2 << endl;
	}
	return 0;
}#include<bits/stdc++.h>

using namespace std;
const int MAXN = 1E5+3;
int n, m, cnt[MAXN];	//cnt用来计数每个点的边数 
int main()
{
	cin >> n >> m; 
	//n个点,m条边 
	for(int i = 1; i <= m; ++i) 
	{
		int a, b;
		cin >> a >> b;
		cnt[a]++;
		cnt[b]++;
	}
	//每个点做遍历 
	int ans = 0;
	for(int i = 1; i <= n; ++i)
	{
		if(cnt[i] % 2 == 1)	//奇点
		{
			ans++;
		} 
	}
	if(ans == 0)
	{
		cout << 1 << endl;
	}
	else
	{
		cout << ans/2 << endl;
	}
	return 0;
}

 

输出格式

一行一个整数,表示答案

输入输出样例

输入 #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 年第八届国赛

这题其实只需要dfs遍历就可以了,和周赛的“大山” 那题很相似。

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

const int MAXN = 1E6+5;
int n, m, k, cnt;
bool vis[MAXN];		//初始化false 
vector<int> arr[MAXN];
void dfs(int start)
{
	for(int i = 0; i < arr[start].size(); ++i)
	{
		int next = arr[start][i];
		if(!vis[next])
		{
			vis[next] = true;
			dfs(next);
		}
	}
}
int main()
{
	cin >> m >> n;
	cin >> k;
	for(int i = 1; i <= k; ++i)
	{
		int a, b;
		cin >> a >> b;
		arr[a].push_back(b);
		arr[b].push_back(a);	//直接安装节点 
	}
	memset(vis, 0, sizeof(vis));
	for(int i = 1; i <= m*n; ++i)
	{
		if(!vis[i])
		{
			dfs(i);
			cnt ++;
			vis[i] = true;
		}
	}
	cout << cnt << endl;
	return 0;
}

 对了,还有个问题,就是能用vector别想着用list或者deque,因为这题我自作聪明用deque结果MLE,用vector就能过。

 

输入输出样例

输入 #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

说明/提示

【输入输出样例 11 说明】

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

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

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

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

输出 Yes

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

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

输出 No

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

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

输出 Yes

【数据规模与约定】

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

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

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

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

这题看起来挺难的,但是其实也能用dfs直接做。思想是生成树的思想,但是只需要从底部和底边相交的孔入手,逐步遍历,直到到达顶部即可。如果不能到达就说明不可能导达。需要记忆化搜索,因为已经走过的点能走的点都会走,如果还在走说明原先的点都不行。需要点数学知识,两圆圆心距离小于等于半径之和就说明其有交集。由于数据范围大所以开longlong比较好。

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

int n, h, ans, r;
bool vis[1001];		//是否走过这个洞 
struct point		//点类坐标 
{
	int x, y, z;
};
point p[1001];

bool check(const point &a, const point &b)
{
	int d = (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z);
	if(d <= 4*r*r)
	{
		return true;
	}
	else
	{
		return false;
	}
}
//寻找点的函数 ,其实就是dfs
void dfs(int x)
{
	if(ans == 1)
		return;
	if(p[x].z + r >= h)
	{
		ans = 1;
		return;
	}
	for(int i = 1; i <= n; ++i)
	{
		if(!vis[i])
		{
			if(check(p[x], p[i]))
			{
				vis[i] = true;
				dfs(i);
			}
		}
	}
}
signed main()
{
	int m;
	cin >> m;
	while(m--)
	{
		cin >> n >> h >> r;
		ans = 0;		//初始化结果为0
		memset(vis, 0, sizeof(vis));
		for(int i = 1; i <= n; ++i)
		{
			cin >> p[i].x >> p[i].y >> p[i].z;
		}
		for(int i = 1; i <= n; ++i)
		{
			if(p[i].z <= r)		//判断是不是底边洞 
			{
				vis[i]  = true;
				dfs(i);
			}
		}
		if(ans)
			cout << "Yes" << endl;
		else
			cout << "No" << endl;
	}
	return 0;
}

 

输入输出样例

输入 #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%的数据,有ti​=0,其中有20%的数据有ti​=0且N>50;

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

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

这题就是弗洛伊德算法的模板题,可是这个有一个所谓的时间的限制,因此我们需要根据时间的限制来逐步用弗洛伊德算法来更新我们的路径长度。弗洛伊德算法的for循环内层的双重循环每进行一次,就可以加入一个顶点。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 202, MAXM = 20002, INF = 0x3f3f3f3f; 
int tmm[MAXN];
int f[MAXN][MAXN];		//存储边的数组 
//bool vis[MAXN], com[MAXN];	//村庄是否被访问和是否建成 
int n, m, q;
//弗洛伊德算法做一次更新 
void floyd(int t) 
{
	for(int i = 0; i < n; ++i) 
	{
		for(int j = 0; j < n; ++j)
		{
			f[i][j] = f[j][i] = min(f[i][j], f[i][t]+f[t][j]);
		}
	}
}
int main()
{
	cin >> n >>m;
	//本题下标从0开始 
	for(int i = 0; i < n; ++i)
	{
		cin >> tmm[i];
	}
	memset(f, INF, sizeof(f));	//这里一定要注意 
	for(int i = 0; i < n; ++i) 
		f[i][i] = 0;
	for(int i = 0; i < m; ++i) 
	{
		int a, b, w;
		cin >> a >> b >> w;
		f[a][b] = f[b][a] = w;
	}
	//因为多次询问所以本题采用弗洛伊德算法来实现 
	cin >> q;
	int now = 0;		//时间是按顺序存储的 
	for(int i = 0; i < q; ++i)
	{
		int x, y, t;
		cin >> x >> y >> t;
		while(tmm[now] <= t && now < n)	//主要循环步骤在这里走的 
		{
			floyd(now++);
		}
		if(tmm[x] > t || tmm[y] > t) 	//有一个超了都不行 
		{
			printf("-1\n");
		}
		else
		{
			if(f[x][y] >= INF)
			{
				printf("-1\n");
			}
			else
			{
				printf("%d\n", f[x][y]);
			}
		}
	}
	return 0;
}

 注意tm在C标准库已经定义了,要改变量的名字。

 

输入输出样例

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

这题就是克鲁斯卡尔算法了。需要用到并查集的概念,用三元组存边,然后sort排序,接下来从头开始挑选边。(从小到大),并查集pre[i]表示i的父节点,并查集的初始化

for(int i = 1; i <= n; ++i) {
		pre[i] = i;
}

并查集的查找

int find(int x) 
{
	if(pre[x] == x)
	{
		return x;
	}
	else
	{
		return pre[x] = find(pre[x]);
	}
}

并查集的合并

pre[s2] = s1;		//合并集合 

代码实现: 

#include <bits/stdc++.h>
using namespace std;
//边类 
//const int INF = 0x3f3f3f3f;
const int MAXM = 502, MAXN = 1002;
struct point
{
	int x, y;
};
struct node
{
	int f, t;
	double w; 
};
double sum = -1;
int mon[MAXM] = {0}, pre[MAXN];	//猴子跳跃的距离和并查集 
point p[MAXN];			//点类 
node nodes[MAXN*MAXN];	//边类 
int m, n;

int find(int x) 
{
	if(pre[x] == x)
	{
		return x;
	}
	else
	{
		return pre[x] = find(pre[x]);
	}
}

int main()
{
	cin >> m;
	for(int i = 1; i <= m; ++i)
	{
		cin >> mon[i];
	}
	cin >> n;
	for(int i = 1; i <= n; ++i)
	{
		cin >> p[i].x >> p[i].y;
	}
	int t = 0;
	for(int i = 1; i <= n; ++i)
	{
		pre[i] = i;
	}
	for(int i = 1; i <= n; ++i) 
	{
		for(int j = 1; j <= n; ++j)
		{
			if(i != j)
			{
				nodes[t].f = i;
				nodes[t].t = j;
				nodes[t].w = sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));
				++t;
			}
		}
	}
	sort(nodes, nodes+t, [](const node &n1, const node &n2)
	{
		return n1.w < n2.w;
	});
	for(int i = 1; i <= n; ++i) 
	{
		pre[i] = i;
	}
	int cnt = n; 					//当前在边集中的边数,只需要点数-1 
	for(int i = 1; i <= t; ++i)//对每条边进行遍历 
	{
		if(cnt == 1)
			break; 
		int s1 = find(nodes[i].f), s2 = find(nodes[i].t);
		if(s1 != s2)			//没有找到就不会成环 
		{
			pre[s2] = s1;		//合并集合 
			cnt--;
			sum = nodes[i].w;	//这个边集是排好序的 
		}
	}
	int ans = 0;
	for(int i = 1; i <= m; ++i) 
	{
		if(sum <= mon[i])
			++ans;
	}
	cout << ans << endl;
	return 0;
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值