第五周
图的存储和遍历
图的存储方式:邻接表、链式前向星
邻接表:用一个vector数组来存储边的信息,数组的索引为边的起点,vector中元素为边的终点
遍历方式:搜索
第一题
题目1
有一个无向图,需要从一个点到另一个点。求所有路径都会经过的点的个数(危险系数)
思路1
存储:使用邻接表。
使用dfs遍历从起点到终点的所有路径,并记录所有点经过的次数。遍历结束后,若某个点被经过的次数等于路径数,则该点关键点。
代码1
#include<iostream>
#include<vector>
using namespace std;
#define N 1001
//邻接表
vector<int> g[N];
//点、边、起点、终点、路径数
int m, n, u, v, pathCount = 0;
//每个点被经过次数
int pointCount[N];
//dfs保存是否走过
bool visited[N];
void dfs(int p)
{
if (p == v)
{
pathCount++;
//更新每个点被经过的次数
for (int i = 1; i <= n; i++)
{
if (visited[i])
{
pointCount[i]++;
}
}
return;
}
for (int i = 0; i < g[p].size(); i++)
{
if (!visited[g[p][i]])
{
visited[g[p][i]] = true;
dfs(g[p][i]);
visited[g[p][i]] = false;
}
}
}
int main()
{
cin >> n >> m;
//生成邻接表
for (int i = 0; i < m; i++)
{
int from, to;
cin >> from >> to;
//无向
g[from].push_back(to);
g[to].push_back(from);
}
cin >> u >> v;
//开始深搜
visited[u] = true;
dfs(u);
//起点和终点不包含,所以提前减掉
int ans = -2;
for (int i = 1; i <= n; i++)
{
if (pointCount[i] == pathCount)
{
ans++;
}
}
//走不通
if (ans < 0)
{
cout << "-1" << endl;
}
//走得通
else
{
cout << ans << endl;
}
return 0;
}
第二题
题目2
有一个有向图,求每个点出发能到达的最大的序号的点
思路2
从每个点开始进行dfs或bfs,记录下搜索到的最大的序号的点。
但这样做较复杂,会超时。
可以将边反向存储,从最大序号的点开始搜索,搜索到的点能到达的最大序号则为当前的序号。这样可以减少搜索次数。
代码2
#include<iostream>
#include<vector>
#include<queue>
#include<string.h>
using namespace std;
#define N 100001
//图
vector<int> g[N];
int m, n;
//是否已经搜索过
int visited[N];
//每个点能到达的最大序号
int maxes[N];
int main()
{
cin >> n >> m;
//生成图
for (int i = 1; i <= m; i++)
{
int from, to;
cin >> from >> to;
//方向存储
g[to].push_back(from);
}
for (int i = n; i > 0; i--)
{
//初始化
memset(visited, 0, sizeof(visited));
//bfs
visited[i] = true;
queue<int> q;
q.push(i);
while (!q.empty())
{
int p = q.front();
q.pop();
//如果这个点还没有被搜索过,则这个点最大序号就是当前的序号
if (maxes[p] == 0)
{
maxes[p] = i;
}
else
{
continue;
}
for (int i = 0; i < g[p].size(); i++)
{
if (!visited[g[p][i]])
{
visited[g[p][i]] = true;
q.push(g[p][i]);
}
}
}
}
for (int i = 1; i <= n; i++)
{
cout << maxes[i] << " ";
}
return 0;
}
第三题
题目3
有一个无向图。用河蟹封锁点,使与该点相连接的所有边都无效。河蟹不能放在相连点上。求使所有边都无效所需最少河蟹。
思路3
一个边要无效,必有一端有河蟹。又因为河蟹不能相连,所以每个边必然一端有河蟹另一端没有。
因此河蟹必须一个隔一个放在途中。用dfs逐个搜索整个图,一隔一放河蟹,并记录放了河蟹的个数和没有放河蟹的个数。如果有一个点既需要放又不能放,则无解。一次搜索后将放河蟹的个数和没有放河蟹的个数的较小值加到答案中(因为放河蟹的地方在另一种方案中就是没有放河蟹的地方,需要放的少的方案,因此选较小值)。将所有点都搜索完,即可得到答案。
代码3
#include<iostream>
#include<vector>
using namespace std;
#define N 10001
//点、边、放了河蟹的个数、没放河蟹的个数、结果
int n, m, put = 0, notput = 0, ans = 0;
//图
vector<int> g[N];
//是否搜索过
bool visited[N];
//在某个点上是否放了河蟹
bool pointPut[N];
//是否有解
bool isPossible = true;
//搜索
void dfs(int p, int cnt)
{
//一隔一,并更新个数
if (cnt % 2 == 0)
{
pointPut[p] = true;
put++;
}
else
{
notput++;
}
//下一个点
for (int i = 0; i < g[p].size(); i++)
{
if (!visited[g[p][i]])
{
visited[g[p][i]] = true;
dfs(g[p][i], cnt + 1);
//只需要遍历一次,因此不需要复原
}
else
{
//如果有冲突
if (pointPut[p] == pointPut[g[p][i]])
{
isPossible = false;
return;
}
}
}
}
int main()
{
cin >> n >> m;
//生成图
for (int i = 0; i < m; i++)
{
int from, to;
cin >> from >> to;
g[from].push_back(to);
g[to].push_back(from);
}
//搜索每一个点
for (int i = 1; i <= n; i++)
{
if (!visited[i])
{
//初始化
put = notput = 0;
visited[i] = true;
dfs(i, 0);
//累加答案
ans += min(put, notput);
}
}
//无解情况
if (!isPossible)
{
cout << "Impossible" << endl;
}
//有解情况
else
{
cout << ans << endl;
}
return 0;
}