图论基础

图是若干个顶点和若干条边构成的数据结构,顶点是实际对象的抽象,边是对象之间关系的抽象。可以将图形式化表示为二元组 G=(V,E),其中,V 是顶点集,表征数据元素;E 是边集,表征数据元素之间的关系。信息学竞赛中一般使用 n 表示图中结点的数量,使用 m 表示图中边的数量。

图可以分为无向图(undirected graph)、有向图(directed graph)、混合图(mixed graph)。无向图的边集 E 中的每个元素是一个无序二元组 (u,v),称作无向边(undirected edge),简称边(edge),其中 uv 称为端点(endpoint)。有向图的边集 E 中的每个元素是一个有序二元组 (u,v),称作有向边(directed edge)或弧(arc),其中 u 称为弧尾,v 称为弧头。混合图中的边集既有无向边也有有向边。

在无向图中,若任意两个顶点之间都存在边,则该无向图称为完全无向图。n 个顶点的无向完全图,一共有 n(n1)/2 条边。在有向图中,若任意两个顶点 x,y,既存在 xy 的弧,也存在 yx 的弧,则该有向图称为有向完全图。n 个顶点的有向完全图,一共有 n(n1) 条弧。

在无向图中,若点 u 与点 v 存在边 (u,v),则顶点 v 和顶点 u 互称为邻接点。在有向图中,若点 u 与点 v 之间存在一条点 u 指向点 v 的一条弧 (u,v),则称顶点 u 邻接到顶点 v,顶点 v 邻接自顶点 u

与顶点相关联的边的数目或者弧的数目称为该顶点的度。在无向图中,顶点的度就是其关联的边的数目。在有向图中,由于与顶点关联的弧具有方向性,因此要区分顶点的入度和出度。入度指以该顶点为弧头的弧的数目,而出度指以该顶点为弧尾的弧的数目,入度与出度之和是该顶点的度。

image

答案

6 号结点,度为 4

例题:P5318 【深基18.例3】查找文献

小 K 喜欢翻看洛谷博客获取知识。每篇文章可能会有若干(也有可能没有)参考文献的链接指向别的博客文章。小 K 求知欲旺盛,如果他看了某篇文章,那么他一定会去看这篇文章的参考文献(如果他之前已经看过这篇参考文献就不用再看它了)。
假设洛谷博客里面一共有 n (n105) 篇文章(编号为 1n)以及 m (m106) 条参考文献引用关系。目前小 K 已经打开了编号为 1 的一篇文章,输出 DFS、BFS 两种遍历方式下看文章的顺序(当有多篇参考文章时,先看编号小的)。

#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
using std::sort;
using std::vector;
using std::queue;
using std::pair;
using Edge = pair<int, int>;
const int N = 1e5 + 5;
const int M = 1e6 + 5;
Edge e[M];
vector<int> g[N];
bool vis[N];
void dfs(int u) {
vis[u] = true; printf("%d ", u);
for (int v : g[u]) {
if (!vis[v]) {
dfs(v);
}
}
}
int main()
{
int n, m; scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y; scanf("%d%d", &x, &y);
e[i] = {x, y};
}
sort(e + 1, e + m + 1); // 将输入的边排序后再真正建图
for (int i = 1; i <= m; i++) {
int x = e[i].first, y = e[i].second;
g[x].push_back(y);
}
dfs(1); printf("\n");
for (int i = 1; i <= n; i++) vis[i] = false; // DFS后BFS前清空标记数组
queue<int> q; q.push(1); vis[1] = true;
while (!q.empty()) {
int u = q.front(); printf("%d ", u); q.pop();
for (int v : g[u]) {
if (!vis[v]) {
q.push(v); vis[v] = true;
}
}
}
printf("\n");
return 0;
}

程序阅读题:

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int MAXN = 200001;
int main() {
int n, m, l, r, w;
cin >> n >> m;
vector <int> dist(MAXN, -1);
vector <bool> vis(MAXN, false);
vector <vector <pair<int, int> > > go(MAXN);
for (int i = 1; i <= m; i++) {
cin >> l >> r >> w;
go[l].push_back(make_pair(r + 1, w));
go[r + 1].push_back(make_pair(l, -w));
}
queue <int> q;
dist[1] = 0; vis[1] = true;
q.push(1);
while (!q.empty()) {
int x = q.front(); q.pop();
for (auto i : go[x]) {
if (!vis[i.first]) {
vis[i.first] = true;
dist[i.first] = dist[x] + i.second;
q.push(i.first);
}
}
}
if (dist[n + 1] == -1) cout << "sorry" << endl;
else cout << dist[n + 1] << endl;
return 0;
}

假设输入的 n,m 是不超过 200000 的正整数,程序第 13 行每次输入的 l,r 保证 lr

判断题

交换程序的第 14 行与第 15 行,不影响程序运行的结果。

答案

正确。第 14 行相当于点 l 向点 r+1 连一条权值为 w 的边,第 15 行相当于点 r+1 向点 l 连一条权值为 w 的边。先连哪条边不影响建图的效果。

输入的 r 的最大值为 n 时,程序可以正常运行。

答案

错误。数组的大小设定为 200001,可以使用的最大下标是 200000,而当输入的 r 达到 200000 时,相当于对应的结点 r+1200001,下标会越界。

在程序的第 17 行至第 29 行,相同的数可能重复进入队列。

答案

错误。进入队列的条件是 !vis[i.first],一旦进入队列后 vis[i.first]=true,因此不可能重复进队。

单选题

当输入的 l 最小值为 x,输入的 r 最大值为 y 时,最多有多少个元素进入过队列?
A. 1 / B. y-x / C. y-x+1 / D. y-x+2

答案

D。这个程序相当于建图后从点 1 开始进行宽度优先搜索。如果输入的每一对 lr 都相同的话,相当于点 ll+1 之间连边。所以进队最多的情况是:x=1,点 1 和点 2 有连边,点 2 和点 3 有连边,以此类推……。那么从 1y+1 都进过队列,共有 y+1x+1=yx+2 个元素。

当输入的 n 为偶数,且 r=l+1 时,m 至少为多少时输出不为 sorry
A. n/2 / B. n/2+1 / C. n/21 / D. n

答案

A。如果 r=l+1,则每次连边的点之间编号正好差 2dist 数组的作用是在宽搜过程中更新从 1 号点到其他点的距离,根据第 30 行至第 31 行,如果最终能够到达 n+1 号点,则会输出这个距离,到不了(最后 dist[n+1] 等于 -1)则输出 sorry。因此要使得 m 尽可能小也就是输入的边数尽可能少,对应的情况是 13 连边,35 连边,以此类推……。由于输入的 n 是偶数,则 n+1 是奇数,需要的边数正好为 n/2

当输入为 5 3 1 3 4 3 4 2 4 5 3 时,输出为?
A. 4 / B. 5 / C. 6 / D. 7

答案

D。根据输入数据建的图如下所示:

image

P2097

#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
using std::sort;
using std::vector;
using std::queue;
const int N = 1e5+5;
vector<int> g[N];
bool vis[N]; // 标记i是否被搜到过
void dfs(int u) {
vis[u]=true;
for (int v : g[u]) {
// u->v
if (!vis[v]) {
dfs(v);
}
}
}
int main()
{
int n,m; scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) {
int u,v; scanf("%d%d",&u,&v);
g[u].push_back(v);
g[v].push_back(u);
}
int ans=0;
for (int i=1;i<=n;i++) {
if (!vis[i]) {
dfs(i);
ans++;
}
}
printf("%d\n",ans);
return 0;
}

P3916

#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
using std::sort;
using std::vector;
using std::queue;
const int N = 1e5+5;
int ans[N];
vector<int> g[N];
bool vis[N]; // 标记i是否被搜到过
void dfs(int u, int source) { // 这个搜索是哪个大编号点发起的
vis[u]=true; ans[u]=source;
for (int v : g[u]) {
// u->v
if (!vis[v]) {
dfs(v, source);
}
}
}
int main()
{
int n,m; scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++) {
int u,v; scanf("%d%d",&u,&v);
g[v].push_back(u); // 反向建图
}
for (int i=n;i>=1;i--) { // 优先从编号大的点发起搜索
if (!vis[i]) {
dfs(i, i);
}
}
for (int i=1;i<=n;i++) printf("%d ", ans[i]);
return 0;
}

P2661

#include <cstdio>
#include <algorithm>
using std::min;
const int N = 2e5+5;
int t[N];
bool vis[N];
int ans[N]; // ans[i]表示从i出发最终会遇到的环的长度
int num[N]; // 递归过程中报数
void dfs(int u, int level) { // level是递归层数,报数
if (vis[u]) {
if (ans[u]==0) { // 第一次找到这个环
ans[u]=level-num[u];
}
return;
}
vis[u]=true; num[u]=level;
dfs(t[u],level+1);
// 回溯
ans[u]=ans[t[u]];
}
int main()
{
int n; scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&t[i]);
for (int i=1;i<=n;i++) {
if (!vis[i]) {
dfs(i,1);
}
}
int r=n;
for (int i=1;i<=n;i++) r=min(r,ans[i]);
printf("%d\n",r);
return 0;
}

例题:P1144 最短路计数

解题思路

无权图最短路问题,可以用 BFS 解决。

ansu 表示 1u 的最短路的数量。

if dis[u] + 1 < dis[v]
ans[v] = ans[u]
else
ans[v] += ans[u]

初始化 ans1=1,认为起点自己到自己,这是一条只有 1 个点不经过任何边的路。

参考代码
#include <cstdio>
#include <vector>
#include <queue>
const int N = 1000005;
const int MOD = 100003;
std::vector<int> g[N];
int dis[N], ans[N];
int main()
{
int n, m; scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) dis[i] = n;
for (int i = 1; i <= m; i++) {
int x, y; scanf("%d%d", &x, &y);
g[x].push_back(y);
g[y].push_back(x);
}
std::queue<int> q;
q.push(1); ans[1] = 1; dis[1] = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) {
if (dis[u] + 1 < dis[v]) {
dis[v] = dis[u] + 1;
q.push(v);
ans[v] = ans[u];
} else if (dis[u] + 1 == dis[v]) {
ans[v] = (ans[v] + ans[u]) % MOD;
}
}
}
for (int i = 1; i <= n; i++) printf("%d\n", ans[i]);
return 0;
}
posted @   RonChen  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示