《算法竞赛入门到进阶》——图论

news2024/9/30 11:27:11

10.1 图的基本概念(P214)

10.2 图的存储(P215)

10.3 图的遍历和连通性(P217)

bfs 和 dfs 。

10.4 拓扑排序(P219)

一个图能进行拓扑排序的充要条件是它是一个有向无环图。

算法思想

这里使用 bfs 求拓扑排序,基本步骤为:

  1. 所有入度为 0 的结点入队。
  2. 弹出队首元素 u ,遍历所有从 u 出发的边,将这些边的终点的入度减一,然后判断其入度是否为 0 ,如果是则将该点入队。
  3. 继续上述操作,直到队列为空。若所有结点都曾经进出队,则图的拓扑排序找到,结点出队顺序即为一个拓扑序;如果某些结点没有入队,则说明该图的拓扑排序不存在。

似乎可以使用拓扑排序来判断一个图是否为 DAG ?

时间复杂度为 O ( V + E ) O(V+E) O(V+E)

特别地,如果题目要求输出字典序最小的拓扑序,则可以考虑使用上述算法中的队列换成优先队列

例1 HDU 1285 确定比赛名次

题目大意

n n n 支队伍,有 m m m 个胜负关系,输出字典序最小的队伍排序。

思路

拓扑排序模板题,由于题目要求输出字典序最小的排序,所以需要使用单调队列。

代码(部分)

// 小根堆
priority_queue< int, vector<int>, greater<int> > q;

void bfs(){
	for(int i=1;i<=n;i++){
		if(!in[i]){
			q.push(i);
		}
	}
	while(!q.empty()){
		int cur = q.top();
		if(first) first = 0;
		else cout<<' ';
		cout<<cur;
		q.pop();
		for(int i=head[cur];i!=0;i=edge[i].next){
			int to = edge[i].to;
			in[to]--;
			if(!in[to]) q.push(to);
		}
	}
	return;
}

例2 POJ 1270 Following Orders

题目大意

给定 n ≤ 30 n\leq30 n30 个元素和 m ≤ 50 m\leq50 m50 个约束关系 x i < y i x_i<y_i xi<yi,按照字典序输出所有满足约束关系的元素序列。

思路

由于本题需要输出所有的拓扑序,所以最好使用 dfs 来实现拓扑排序,具体实现见代码。

代码(部分)

void topu(int x, int d){
	ans[d] = x;
	vis[x] = 1;
	if(d == cnt){
		for(int i=1;i<=cnt;i++){
			cout<<(char)(ans[i]-1+'a');
		}
		cout<<'\n';
		return;
	}
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to;
		in[to]--;
	}
	for(int i=1;i<=cnt;i++){
		if(in[a[i]]==0 && !vis[a[i]]){
			topu(a[i], d+1);
		}
	}
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to;
		in[to]++;
	}
	vis[x] = 0;
}

例3 HDU 4857 逃生

题目大意

输出这样的拓扑序:1 号尽量靠前,然后让 2 号尽量靠前,以此类推。

思路

参考:https://blog.csdn.net/AC__dream/article/details/120235928

在刚拿到这个题的时候,我误认为输出字典序最小的拓扑序即可,但其实题目要求的并不是这样。

在这里插入图片描述

从上图的例子我们可以看出,如果字典序最小的拓扑序是:1, 3, 4, 2, 5 ,而真正的答案是:1, 4, 2, 3, 5 ,因为后者 2 的位置前者更靠前。我们注意到这样一个事实:如果 1i-1 的位置固定,i 的位置越靠前,则该序列的逆序列的字典序越大

所以,我们可以建反图,然后得到反图中字典序最大的拓扑序,最后反着输出即可。

本题的输入会有重边的现象,但不会造成影响。

代码(部分)

priority_queue< int, vector<int>, less<int> > q;

void topu(){
	for(int i=1;i<=n;i++){
		if(in[i] == 0){
			q.push(i);
		}
	}
	while(!q.empty()){
		int cur = q.top();
		q.pop();
		s.push(cur);
		for(int i=head[cur];i!=0;i=edge[i].next){
			int to = edge[i].to;
			in[to]--;
			if(in[to] == 0){
				q.push(to);
			}
		}
	}
}

例4 HDU 1811 Rank of Tetris

题目大意

给定 n < 10000 n<10000 n<10000 个选手,给定 m < 20000 m<20000 m<20000 个形如: A > B A>B A>B A = B A=B A=B A < B A<B A<B 的约束条件,表示 A、B 两个选手之间的得分关系。规定:如果两个选手得分相同,则序号大者排名高。问:根据这些约束条件能否唯一确定一个排名?是否存在冲突?

思路

相等关系用并查集维护,不等关系则对应一条有向边;先处理所有相等关系,再处理不等关系。进行拓扑排序时,如果某一时刻同时存在两个及以上结点的入度为 0 0 0 ,说明排名不确定;如果有些结点从未被访问过,则说明存在冲突。

代码

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

const int maxn = 1e4+5;
const int maxm = 2e4+5;

int n, m;
int f[maxn], siz[maxn];
int head[maxn];
int in[maxn];
int tot = 0;
set<int> s;
bool uncertain = 0, conflict = 0;
int sum = 0;

struct NODE{
	int u, v;
	char c;
};
NODE node[maxm];

struct EDGE{
	int to;
	int next;
};
EDGE edge[maxm];

void addEdge(int fr, int to){
	tot++;
	edge[tot].to = to;
	edge[tot].next = head[fr];
	head[fr] = tot;
	in[to]++;
}

void init(){
	uncertain = conflict = 0;
	s.clear();
	sum = tot = 0;
	memset(head, 0, sizeof(head));
	memset(in, 0, sizeof(in));
	for(int i=0;i<n;i++){
		f[i] = i;
		siz[i] = 1;
	}
} 

int find(int x){
	return x == f[x] ? x : f[x]=(find(f[x]));
}

void merge(int x, int y){
	if(siz[x] < siz[y]){
		f[x] = y;
		siz[y] += siz[x];
	}
	else{
		f[y] = x;
		siz[x] += siz[y];
	}
}

void topu(){
	int cnt = 0;
	for(int i=0;i<n;i++){
		int x = find(i);
		if(i==x && in[x]==0){
			s.insert(x);
		}
	}
	while(!s.empty()){
		if(s.size() > 1){
			uncertain = 1;
		}
		int cur = *s.begin();
		cur = find(cur);
		s.erase(s.begin());
		cnt++;
		for(int i=head[cur];i!=0;i=edge[i].next){
			int to = edge[i].to;
			to = find(to);
			in[to]--;
			if(in[to] == 0){
				s.insert(to);
			}
		}
	}
	if(cnt != sum){
		conflict = 1;
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	while(cin>>n>>m){
		init();
		for(int i=1;i<=m;i++){
			cin>>node[i].u>>node[i].c>>node[i].v;
		}
		for(int i=1;i<=m;i++){
			int x = find(node[i].u), y = find(node[i].v);
			if(node[i].c == '='){
				if(x == y) continue;
				else merge(x, y);
			}
		}
		for(int i=1;i<=m;i++){
			int x = find(node[i].u), y = find(node[i].v);
			if(node[i].c == '<'){
				addEdge(y, x);
			}
			else if(node[i].c == '>'){
				addEdge(x, y);
			}
		}
		for(int i=0;i<n;i++){
			if(i == find(i)){
				sum++;
			}
		}
		topu();
		if(conflict) cout<<"CONFLICT\n";
		else if(uncertain) cout<<"UNCERTAIN\n";
		else cout<<"OK\n";
	}
	return 0;
}

10.5 欧拉路和欧拉回路(P223)

欧拉路和欧拉回路是否存在

  • 无向图:如果所有结点的度均为偶数,则存在欧拉回路,任意结点均可以作为起点;如果有且仅有两个结点的度为奇数,则存在欧拉路,两个奇数点分别作为起点和终点。
  • 有向图:定义有向图中,结点的度为出度与入度之差。如果所有结点的度均为 0 0 0 ,则存在欧拉回路;如果只有一个结点的度为 1 1 1 ,一个结点的度为 − 1 -1 1 ,其余结点的度均为 0 0 0 ,则存在欧拉路。

输出一个欧拉回路

例子见 P224 。

void dfs(int x){
	for(int i=1;i<=maxc;i++){
		if(dis[x][i]){
			// 这条边已经走过,之后不能再走
			dis[x][i]--, dis[i][x]--;
			dfs(i);
			cout<<i<<" "<<x<<"\n";
		}
	}
}

如果欧拉回路过长,则 dfs 很有可能出现“爆栈”的情况,这时候就需要使用非递归的 dfs 算法。非递归 dfs 实现简单地说就是把 不同bfs 的队列换成

10.6 无向图的连通性(P225)

10.6.1 割点和割边(P225)

在一个无向图中,能够相互连通的结点构成一个连通块,如果删除连通块中的某个结点,会导致其他结点不再相互连通,则这个点称为割点。具有相似性质的边称为割边

我们用下面两条定理引出求割点的 tarjan 算法:

  • 对于一颗 dfs 生成树 T T T 的根节点 s s s s s s 是割点当且仅当 s s s两个及以上的子结点。
  • 对于一颗 dfs 生成树 T T T 的非根节点 u u u u u u 是割点当且仅当 u u u 存在某一个子结点 v v v v v v 及其后代都没有回退边连回 u u u 的祖先。

例1 P3388 【模板】割点(割顶)

题目大意

给定一个无向图,输出割点个数和所有割点的编号。

思路

tarjan 求割点模板题。

代码

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

const int maxn = 2e4+5;
const int maxm = 1e5+5;

int n, m;
int head[maxn];
int tot = 0;
int idx = 0;
int dfn[maxn], low[maxn];
int cnt = 0;
int ans[maxn];
bool isCut[maxn];

struct EDGE{
	int to;
	int next;
};
EDGE edge[maxm<<1];

void addEdge(int fr, int to){
	tot++;
	edge[tot].to = to;
	edge[tot].next = head[fr];
	head[fr] = tot;
}

void tarjan(int x, int fa){
	idx++;
	int child = 0;
	dfn[x] = low[x] = idx;
	for(int i=head[x];i!=0;i=edge[i].next){
		int to = edge[i].to;
		if(to == fa) continue;
		if(!dfn[to]){
			child++;
			tarjan(to, x);
			low[x] = min(low[x], low[to]);
			if(low[to]>=dfn[x] && x!=fa){
				isCut[x] = 1;
			}
		}
		else{
			low[x] = min(low[x], dfn[to]);
		}
	}
	if(x==fa && child>=2){
		isCut[x] = 1;
	}
	if(isCut[x]) cnt++;
}

void solve(){
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			// 当且仅当x为根节点时,有x==fa
			tarjan(i, i);
		}
	}
	cout<<cnt<<'\n';
	for(int i=1;i<=n;i++){
		if(isCut[i]) cout<<i<<' ';
	}
	return; 
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u, v;
		cin>>u>>v;
		addEdge(u, v);
		addEdge(v, u);
	}
	solve();
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1309131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据----31.hbase安装启动

二.Hbase安装 先前安装&#xff1a; Zookeeper 正常部署 首先保证 Zookeeper 集群的正常部署&#xff0c;并启动之。 三台机器都执行&#xff1a;zkServer.sh startHadoop 正常部署 Hadoop 集群的正常部署并启动。 主节点上进行 &#xff1a;start-all.sh 1.HBase 的获取 一定…

AUTOSAR_SWS_LogAndTrace文档中文翻译

1 Introduction and functional overview 本规范规定了AUTOSAR自适应平台日志和跟踪的功能。 日志和跟踪为AA提供接口&#xff0c;以便将日志信息转发到通信总线、控制台或文件系统。 提供的每个日志记录信息都有自己的严重性级别。对于每个严重级别&#xff0c;都提供了一个单…

风速预测(三)EMD-LSTM-Attention模型

目录 1 风速数据EMD分解与可视化 1.1 导入数据 1.2 EMD分解 2 数据集制作与预处理 2.1 先划分数据集&#xff0c;按照8&#xff1a;2划分训练集和测试集 2.2 设置滑动窗口大小为7&#xff0c;制作数据集 3 基于Pytorch的EMD-LSTM-Attention模型预测 3.1 数据加载&#…

【数据结构】什么是堆?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 堆的概念及结构 堆的定义如下: n个元素的序列{k1,k2,...,kn}当且仅当满足以下关系时,称之为堆. 或 把这个序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个…

贪心算法:K次取反后最大化的数组和 加油站 分发糖果

1005.K次取反后最大化的数组和 思路&#xff1a; 如果数组中有负数的话&#xff0c;优先把较小那些的负数取反变成整数&#xff1b;如果没有负数了&#xff0c;而k的次数还有剩&#xff0c;只对最小的正数反复做取反&#xff0c;即使这个数最后变成了负数&#xff0c;也是能取…

HarmonyOS使用Web组件

Web组件的使用 1 概述 相信大家都遇到过这样的场景&#xff0c;有时候我们点击应用的页面&#xff0c;会跳转到一个类似浏览器加载的页面&#xff0c;加载完成后&#xff0c;才显示这个页面的具体内容&#xff0c;这个加载和显示网页的过程通常都是浏览器的任务。 ArkUI为我…

ARCGIS 中使用 ChatGPT 的 5 种方式

ChatGPT 一度成为最热门的话题。什么是 ChatGPT&#xff1f;谁能比 ChatGPT 本身更好地回答这个问题呢&#xff1f;我们要求它写一个关于 ChatGPT 是什么的简短描述&#xff0c;这是它的回应&#xff1a; ChatGPT 是一个聊天机器人&#xff0c;使用 OpenAI 开发的 GPT-3 语言模…

完全平方数 C语言xdoj49

问题描述 若一个整数n能表示成某个整数m的平方的形式&#xff0c;则称这个数为完全平方数。写一个程序判断输入的整数是不是完全平方数。 输入说明 输入数据为一个整数n&#xff0c;0<n<10000000。 输出说明 如果n是完全平方数&#xff0c;则输出构成这个完全…

扩展操作码指令格式

指令 操作码地址码 \quad \quad 判断几地址指令 开头4位不是全1, 则表示是三地址指令 开头4位全1, 后面4位不是全1, 则为二地址指令 前面12全1, 则为零地址指令 当然啦这只是一种扩展方法, 如果想扩展更多, 可以将1110留作扩展操作码 较短的操作码, 我们对它的译码和分析的时间…

@ApiImplicitParam注解使用说明

ApiImplicitParam注解使用说明 ApiImplicitParam是Swagger注解之一&#xff0c;用于描述接口中的参数信息。它可以用于方法上的单个参数或者方法上的参数列表。具体来说&#xff0c;它可以描述参数的名称、数据类型、是否必填、参数的具体意义等信息。 下面是一个使用ApiImpl…

让艺术触手可及!实时云渲染赋能真浪数字艺术馆首展

2023年5月18日&#xff0c;由真浪数字艺术和EZVR联合打造的真浪数字艺术馆首展–「破界交织」让艺术更自由&#xff0c;正式与大家相见。此次展览分为五个主题展馆&#xff0c;汇聚了来自全球各领域的19位青年数字艺术家一同探讨虚实共生、人机共生和万物共生的艺术创作。 真浪…

发布 Whatsonchain 上的 BSV20 插件

我们发布了 whatsonchain 上的 BSV20 插件来验证 BSV20 代币。 对于任何交易&#xff0c;whatsonchain 都可以通过以下网址打开&#xff1a; https://whatsonchain.com/tx/{hash}我们使用此 bsv20 v21 交易 打开 Whatsonchain 。 打开whatsonchain后你会看到BSV20插件&#x…

VLAN协议与单臂路由

文章目录 VLAN协议与单臂路由一、VLAN的概念及优势1、分割广播域2、VLAN的优势3、VLAN数据帧 二、VLAN的种类1、静态VLAN2、动态VLAN3、VLAN划分方式 三、静态VLAN的配置1、VLAN的范围2、静态VLAN的配置2.1 配置静态VLAN的步骤2.2 vlan三种端口类型举例&#xff1a;配置静态VLA…

Vue学习计划-Vue2--VueCLi(一)准备工作,安装node、vuecli

1. 安装node 网址&#xff1a;https://nodejs.org/en下载LTS版本表示长期支持版本说明&#xff1a; node是一个基于Chrome V8引擎的javascript运行环境,让JavaScript 运行在服务端的开发平台vuecli创建的项目必须运行在node环境中&#xff0c;npm为node自带包管理工具&#xf…

java 执行linux 命令

文章目录 前言一、linux命令执行二、使用步骤三、踩坑 前言 java 执行linux 命令&#xff1b; 本文模拟复制linux文件到指定文件夹后打zip包后返回zip名称&#xff0c;提供给下载接口下载zip&#xff1b; 一、linux命令执行 linux命令执行Process process Runtime.getRunti…

FlieZilla服务器配置与数据访问、传输

概述 手机apk当初服务器&#xff0c;PC端访问手机端的数据&#xff0c;再没有数据线的情况下&#xff0c;非常方便。希望各位同仁搞起来&#xff0c;在此做个笔录。 安装包下载链接&#xff1a;https://download.csdn.net/download/qq_36075612/88577274 一、下载安装包&…

​Linux Ubuntu环境下安装配置Docker 和Docker、compose、mysql、中文版portainer

​Linux Ubuntu环境下安装配置Docker 和Docker、compose、mysql、中文版portainer 这篇文章探讨了在Linux Ubuntu环境下安装和配置Docker及其相关工具的过程。首先介绍了Docker的基本概念&#xff0c;然后详细讲解了在Ubuntu系统上的安装步骤。随后&#xff0c;文章涵盖了Dock…

【JUC】二十八、synchronized锁升级之偏向锁

文章目录 1、偏向锁出现的背景2、从共享对象的内存结构看偏向锁3、偏向锁的持有4、启动偏向锁5、sleep暂停来启动偏向锁6、偏向锁的撤销7、总体流程8、SinceJava15 偏向锁的废除 1、偏向锁出现的背景 如果一个线程连续几次抢到锁&#xff0c;仍然重复加锁解锁&#xff0c;就会…

如何使用 Redis 快速实现分布式锁?

本文我们来讨论如何使用 Redis 快速实现分布式锁。 分布式锁有很多种解决方案&#xff0c;前面简单介绍过&#xff0c;Redis 可以通过 set key 方式来实现分布式锁&#xff0c;但实际情况要更加复杂&#xff0c;比如如何确保临界资源的串行执行&#xff0c;如何及时释放&#…

HarmonyOS、ArkTS 备忘录(持续更新中)

Component 、Builder Component封装大的组件Builder自定义构建函数&#xff0c;可以理解为 构建页面的函数&#xff1b;Builder插槽多点&#xff0c;封装一些小的模块 组件状态管理 像素单位 export default 和 export之间的区别