图论的整合

有若干个节点,有若干条边连接节点。(两个点之间不是必须相连)
比如:
在这里插入图片描述

有向图

可以理解为边上面有箭头的图,比如下面这张图:
在这里插入图片描述
在这张图中,点 1 1 1 可以通过这条有向边到达点 2 2 2,但是点 2 2 2 到达不了点 1 1 1
有向图的边是有单向性的。

无向图

只要连了边,边两端的点都可以互相到达。
在这里插入图片描述

这张图是无向图,里面任一点都可以互相到达。
这就是一张连通图。

连通图

一张无向图,如果任意一个点都可以到达图上的所有点,那么这张无向图就是连通图。
如图1

强连通图

一张有向图,如果任意两个点都可以互相到达,这就是一张强连通图。
在这里插入图片描述比如上面的这张图就是不联通的,不是强连通图。
如果再加上一条边:
在这里插入图片描述
现在这就是一张强连通图了。

带权图

可以理解为图中的边被加上了一个权值,经过这条边就要付出对应权值的代价。
比如:
在这里插入图片描述
在这张图中,点 1 1 1 走到点 4 4 4 的总权值是 3 + 5 + 2 = 10 3+5+2=10 3+5+2=10

图的存储

邻接矩阵

假设有一个 N N N 个点的图,我们可以开一个 N ∗ N N*N NN 的二维数组 a a a a i j a_{ij} aij 表示点 i i i 到点 j j j 有没有边。
如果是带权图,也可以用 a i j a_{ij} aij 表示点 i i i 到点 j j j 边的权值。

例题1

题目描述
给出一个无向图,顶点数为 n n n,边数为 m m m n ≤ 1000 , m ≤ 10000 n\le1000,m\le10000 n1000,m10000
输入格式
第一行两个整数 n , m n,m n,m,接下来有 m m m 行,每行两个整数 u , v u,v u,v,表示点 u u u 到点 v v v 有一条边。
输出格式
由小到大依次输出每个点的邻接点,邻接点也按照由小到大的顺序输出。

首先 n n n 只有 1000 1000 1000,可以用邻接矩阵存图。
是无向图,所以 u u u v v v v v v u u u 都有一条边,所以要双向存。

#include<bits/stdc++.h>
#define psp putchar(' ')
#define endl putchar('\n')
using namespace std;
const int M=1e3+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
int n,m;
int a[M][M];
signed main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		//双向存边
		a[u][v]=1;
		a[v][u]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j])print(j),psp;//有边,输出。
		}
		endl;
	}
}
邻接表

假如一张图有 2048 2048 2048 个点,那么用邻接矩阵就要开 204 8 2 2048^2 20482 的数组,那如果这张图只有 10 10 10 条边,开这么大的空间就全浪费了。
这时候就要用到邻接表。
可以用动态数组来存点和边,减少空间的浪费。

vector<int>a[N];
例题2

题目描述
给出一个无向图,顶点数为 n n n,边数为 m m m n ≤ 1000 , m ≤ 10000 n\le1000,m\le10000 n1000,m10000
输入格式
第一行两个整数 n , m n,m n,m,接下来有 m m m 行,每行两个整数 u , v u,v u,v,表示点 u u u 到点 v v v 有一条边。
输出格式
第i行输出第点 i i i 的所有邻接点,邻接点按照度数由小到大输出,如果度数相等,则按照编号有小到大输出。

按照度数排序,用邻接表更方便。

#include<bits/stdc++.h>
#define endl putchar('\n')
using namespace std;
const int N=1e6+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
int n,m;
vector<int>a[N];
int d[N];
bool cmp(int a,int b){//按照度数排序
	return d[a]<d[b]||(d[a]==d[b]&&a<b);
}
signed main(){
	n=read(),m=read();
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		//邻接表存图
		a[u].push_back(v);
		a[v].push_back(u);
		d[u]++,d[v]++;
	}
	for(int i=1;i<=n;i++){
		sort(a[i].begin(),a[i].end(),cmp);
		for(int j=0;j<a[i].size();j++)print(a[i][j]),psp;
		endl;
	}
}
例题3

题目描述
K ( 1 ≤ K ≤ 100 ) K(1\le K\le100) K(1K100) 只奶牛分散在 N ( 1 ≤ N ≤ 1000 ) N(1\le N\le1000) N(1N1000) 个牧场。现在她们要集中起来进餐。
牧场之间有 M ( 1 ≤ M ≤ 10000 ) M(1\le M\le10000) M(1M10000) 条有向路连接,而且不存在起点和终点相同的有向路。她们进餐的地点必须是所有奶牛都可到达的地方。
那么,有多少这样的牧场呢?
输入格式
第1行输入 K , N , M K,N,M K,N,M
接下来 K K K 行,每行一个整数表示一只奶牛所在的牧场编号。
接下来 M M M 行,每行两个整数,表示一条有向路的起点和终点。
输出格式
所有奶牛都可到达的牧场个数。

由于 N N N 最大只有 1000 1000 1000 K K K 最大只有 100 100 100,所以可以考虑枚举每一头牛,然后记他们可以到达的点,最后的答案就有所有牛都能到达点的数量。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
int n,m,k;
int cow[N];
vector<int>a[N];
int vis[N];
int dis[N];//dis[i]表示点i有几头牛可以到达
signed main(){
	k=read(),n=read(),m=read();
	for(int i=1;i<=k;i++)cow[i]=read();
	while(m--){
		int u=read(),v=read();
		a[u].push_back(v);
	}
	for(int i=1;i<=k;i++){
		queue<int>q;
		for(int j=1;j<=n;j++)vis[j]=0;
		vis[cow[i]]=1;
		q.push(cow[i]);
		while(!q.empty()){
			int x=q.front();
			q.pop();
			dis[x]++;//记录
			for(int p=0;p<a[x].size();p++){
				int y=a[x][p];
				if(vis[y])continue;
				vis[y]=1;
				q.push(y);
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)ans+=(dis[i]==k);
	print(ans);
}
例题4

P3144 [USACO16OPEN] Closing the Farm S
依次遍历每个仓库关闭,检查是否能遍历到除关闭仓库外的所有仓库。

#include<bits/stdc++.h>
#define endl putchar('\n')
using namespace std;
const int N=1e6+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void putstr(string s){
	for(char v:s)putchar(v);
}
int n,m;
vector<int>a[N];
int cl[N];
int vis[N];
signed main(){
	n=read(),m=read();
	while(m--){
		int u=read(),v=read();
		a[u].push_back(v);
		a[v].push_back(u);
	}
	int st=1;
	for(int i=1;i<=n;i++){
		while(cl[st])st++;
		queue<int>q;
		q.push(st);
		for(int i=1;i<=n;i++)vis[i]=0;
		vis[st]=1;
		while(!q.empty()){
			int x=q.front();
			q.pop();
			for(int i=0;i<a[x].size();i++){
				int y=a[x][i];
				if(vis[y])continue;
				if(cl[y])continue;
				vis[y]=1;
				q.push(y);
			}
		}
		bool flag=1;
		for(int i=1;i<=n;i++){
			if(cl[i])continue;
			if(!vis[i])flag=0;
		}
		cl[read()]=1;
		if(flag)putstr("YES");
		else putstr("NO");
		endl;
	}
}

最短路

一张带权图,一个点 x x x 到另一个点 y y y 所经过边的最小权值和称为点 x x x 到点 y y y 的最短路。

Dijkstra

一种高效的求最短路的算法,缺点是不能求带负权的最短路。
它的主要用途是求一个点到图中其他点的最短路。
具体过程
最开始,除了起点外所有点没有被标记过,起点是最开始被选中的点。
开始模拟:找到被选中的点连接的最小边权的且没有被标记过的点 y y y,记录最短路,然后点 y y y 被标记,并成为下一个被选中的点,直到所有点都被标记。

例题5

P4779 【模板】单源最短路径(标准版)
具体代码见单源最短路径

SPFA

讲解+例题见带负权的最短路问题


  1. 无向图 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值