图论基础

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

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

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

在无向图中,若点 \(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 \ (n \le 10^5)\) 篇文章(编号为 \(1\)\(n\))以及 \(m \ (m \le 10^6)\) 条参考文献引用关系。目前小 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\) 保证 \(l \le r\)

判断题

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

答案

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

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

答案

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

在程序的第 \(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\) 开始进行宽度优先搜索。如果输入的每一对 \(l\)\(r\) 都相同的话,相当于点 \(l\)\(l+1\) 之间连边。所以进队最多的情况是:\(x=1\),点 \(1\) 和点 \(2\) 有连边,点 \(2\) 和点 \(3\) 有连边,以此类推……。那么从 \(1\)\(y+1\) 都进过队列,共有 \(y+1-x+1=y-x+2\) 个元素。

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

答案

A。如果 \(r=l+1\),则每次连边的点之间编号正好差 \(2\)dist 数组的作用是在宽搜过程中更新从 \(1\) 号点到其他点的距离,根据第 \(30\) 行至第 \(31\) 行,如果最终能够到达 \(n+1\) 号点,则会输出这个距离,到不了(最后 dist[n+1] 等于 -1)则输出 sorry。因此要使得 \(m\) 尽可能小也就是输入的边数尽可能少,对应的情况是 \(1\)\(3\) 连边,\(3\)\(5\) 连边,以此类推……。由于输入的 \(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;
}
posted @ 2024-08-14 14:11  RonChen  阅读(33)  评论(0编辑  收藏  举报