一、【P5318】查找文献(邻接表+DFS+BFS)
本题是图的遍历模板题,需要使用DFS和BFS遍历方法。
由于本题最多有1e5个顶点,如果采用邻接矩阵存储图的话需要4*1e10 Byte空间,显然会超内存,因此这里采用邻接表的方法存储。但需要注意的是,本题并没有使用广义上的邻接表,而是使用vector和array结合的方式存储,先用Edge数组存储边,并赋予每条边一个序号,然后用二维vector数组模拟邻接表,一维空间存储顶点的编号,二维空间存储该点所连接的边的序号。
1.1 图的邻接表存储
8 9
1 2 //0号边(由于vector的下标是从0开始的,咱就“入乡随俗”,从0开始)
1 3 //1号边
1 4 //2号边
2 5 //3号边
2 6 //4号边
3 7 //5号边
4 7 //6号边
4 8 //7号边
7 8 //8号边
最后二维vector中的存储会如下所示:
0 1 2 //1号顶点连着0、1、2号边
3 4 //2号顶点连着3、4号边
5 //3号顶点连着5号边
6 7 //4号顶点连着6、7号边
//5号顶点没有边
//6号顶点没有边
8 //7号顶点连着8号边
//8号顶点没有边
1.2 DFS深搜
void DFS(int cur)
{
visit[cur] = 1;
cout << cur << " ";
for (int i = 0; i < arr[cur].size(); i++)
{
int dot = Edge[arr[cur][i]].v;
if (!visit[dot])
DFS(dot);
}
}
1.3 BFS广搜
void BFS(int cur)
{
queue<int> q;
q.push(cur);
cout << cur << " ";
visit[cur] = 1;
while (!q.empty())
{
int f = q.front();
for (int i = 0; i < arr[f].size(); i++)
{
int dot = Edge[arr[f][i]].v;
if (!visit[dot])
{
q.push(dot);
cout << dot << " ";
visit[dot] = 1;
}
}
q.pop();
}
}
1.4 AC代码
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <cstring>
#include <set>
#include <queue>
using namespace std;
vector<int> arr[100005];
struct edge
{
int u, v;
};
vector<edge> Edge;
bool visit[100005] = { 0 };
void DFS(int cur)
{
visit[cur] = 1;
cout << cur << " ";
for (int i = 0; i < arr[cur].size(); i++)
{
int dot = Edge[arr[cur][i]].v;
if (!visit[dot])
DFS(dot);
}
}
void BFS(int cur)
{
queue<int> q;
q.push(cur);
cout << cur << " ";
visit[cur] = 1;
while (!q.empty())
{
int f = q.front();
for (int i = 0; i < arr[f].size(); i++)
{
int dot = Edge[arr[f][i]].v;
if (!visit[dot])
{
q.push(dot);
cout << dot << " ";
visit[dot] = 1;
}
}
q.pop();
}
}
bool cmp(edge x, edge y)
{
return x.v < y.v;
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int a, b;
cin >> a >> b;
edge s; s.u = a; s.v = b;
Edge.push_back(s);
}
sort(Edge.begin(), Edge.end(), cmp); //容器无法用指针,只能用迭代器
for (int i = 0; i < m; i++)
{
arr[Edge[i].u].push_back(i);
}
DFS(1);
cout << endl;
memset(visit, 0, sizeof(visit));
BFS(1);
}
二、【P1113】杂物(拓扑排序+动态规划)
本题显然是一个拓扑排序的模板题,需要先完成某个任务的先决任务后才能再完成该任务。
那么如何统计总的耗时呢?可以使用记忆化搜索(动态规划)的方法,完成一个任务后,更新所有后继任务的耗时,需要注意的是更新时要选择最长的时间,因为完成所有先决条件的时间一定比完成部分先决条件的用时更长。
2.1 拓扑排序
总结:
- 初始化队列,将入度为 00 的节点放入队列。
- 取出队首,遍历其出边,将能够到达的点入度减一,同时维护答案数组。
- 若在此时一个点的入度变为 11,那么将其加入队列。
- 回到第二步,直到队列为空。
2.2 AC代码
1.输入杂物耗时,并用vector存下它的后继杂物。并用数组ind[i]存下第 i 个杂物的先决杂物个数(即入度)。
2.将所有入度为0的点 i 加入队列,并将dp[i](表示第 i 个杂物的最早完成时间)的值置为第 i 个杂物的耗时stuff[i].len。
3.只要队列不空,拿出队列顶端的杂物cur,并用cur更新以cur为先决杂物的杂物nxt的dp[nxt]的值。使dp[nxt]=max(dp[nxt],dp[cur]+stuff[nxt].len)。再将ind[nex]−−。如果ind[nex]为0,将点nxt加入队列。
4.求出所有dp[i]的最大值ans,就是答案。
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <cstring>
#include <set>
#include <queue>
using namespace std;
struct node
{
int len;
vector<int> next;
}stuff[10005];
queue<int> q;
int dp[10005] = { 0 };
int ind[10005] = { 0 };
int main()
{
int n; cin >> n;
for (int i = 1; i <= n; i++)
{
int No; cin >> No;
cin >> stuff[i].len;
int tmp; cin >> tmp;
while (tmp != 0)
{
ind[i] += 1;
stuff[tmp].next.push_back(i);
cin >> tmp;
}
}
//1.初始化队列
for (int i = 1; i <= n; i++)
{
if (ind[i] == 0)
{
q.push(i);
dp[i] = stuff[i].len;
}
}
//2.不断取出队首,删除出度边
while (!q.empty())
{
int cur = q.front();
q.pop();
for (int i = 0; i < stuff[cur].next.size(); i++)
{
int nxt = stuff[cur].next[i];
ind[nxt]--;
//3.当入度为0时加入队列
if (ind[nxt] == 0) q.push(nxt);
dp[nxt] = max(dp[nxt], dp[cur] + stuff[nxt].len);//为什么是max?如果dp值小说明有其他先决条件没有完成
}
}
int ans = -1;
for (int i = 1; i <= n; i++)
ans = max(ans, dp[i]);
cout << ans << endl;
}
三、【P1363】幻象迷宫(DFS)
DFS或者BFS均可实现,问题在于这里变成了无限大的迷宫,出现了什么样的状况就是无限大了呢?一开始想比如穿越了边界是不是就能走到无限远了,但显然不是,因为穿越边界之后有可能仍然是死路,比如说上面的样例2 。
正确的条件应该是,当走到(modx,mody)时,仍然记录到达此点的真实的x和y,当再一次走到(x mod n,y mod m)时,如果此时的真实的x和y不同,说明已经穿越了若干个完整的地图了,接下来只需和刚才走一样的路线就一定能走无限远了。
3.1 DFS注意事项
- 由于内存限制,想要记住所有走过的点是不现实的,所以我们只能记住最近一次走到迷宫上某点时的真实位置。
- 需要注意的是cur_x和cur_y可能会是一个很大的负数,所以导致越界访问,需要确保得到的pos_x和pos_y是正数。
3.2 AC代码
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
#include <map>
using namespace std;
int n, m;
bool mp[1505][1505];
int dx[4] = { -1,1,0,0 };
int dy[4] = { 0,0,-1,1 };
bool flag = false;
void DFS(map<pair<int, int>, pair<int, int>> &visit,int pos_x,int pos_y,int cur_x,int cur_y)
{
if (flag) return;
if (visit.count({ pos_x,pos_y }) && (visit[{pos_x, pos_y}].first != cur_x || visit[{pos_x, pos_y}].second != cur_y))
{
flag = true;
return;
}
if (visit.count({ pos_x,pos_y }) && visit[{pos_x, pos_y}].first == cur_x && visit[{pos_x, pos_y}].second == cur_y) return;
visit[{pos_x, pos_y}] = { cur_x,cur_y };
for (int i = 0; i < 4; i++)
{
int next_x = (pos_x + dx[i] + n) % n;//如果cur_x是一个较大负数,那么就会导致越界
int next_y = (pos_y + dy[i] + m) % m;//同理,必须用pos_y保证是正数
if (mp[next_x][next_y])
DFS(visit, next_x,next_y,cur_x + dx[i], cur_y + dy[i]);
}
}
int main()
{
while (cin >> n >> m)
{
int Si = 0, Sj = 0;
memset(mp, false, sizeof(mp));
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
char tmp; cin >> tmp;
if (tmp == 'S')
{
Si = i; Sj = j;
mp[i][j] = true;
}
else if (tmp=='.')
{
mp[i][j] = true;
}
}
}
map<pair<int, int>, pair<int, int>> visit;
flag = false;
DFS(visit, Si, Sj, Si, Sj);
if (flag) cout << "Yes" << endl;
else cout << "No" << endl;
}
}