首页 > 其他分享 >24暑假集训day5下午

24暑假集训day5下午

时间:2024-08-03 17:17:14浏览次数:9  
标签:24 int day5 DFS 节点 BFS vis include 集训

DFS

本质:一种用于遍历或搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走。

该算法讲解时常常与 BFS 并列,但两者除了都能遍历图的连通块以外,用途完全不同,很少有能混用两种算法的情况。

关键:

递归调用自身

对其访问过的点打上访问标记,在遍历图时跳过已打过标记的点,以确保 每个点仅访问一次

伪代码:

DFS(v) // v 可以是图中的一个顶点,也可以是抽象的概念,如 dp 状态等。
  在 v 上打访问标记
  for u in v 的相邻节点
    if u 没有打过访问标记 then
      DFS(u)
    end
  end
end

性质

该算法通常的时间复杂度为 \(O(n+m)\),空间复杂度为 \(O(n)\),其中 \(n\) 表示点数,\(m\) 表示边数。注意空间复杂度包含了栈空间,栈空间的空间复杂度是 \(O(n)\) 的。在平均 \(O(1)\) 遍历一条边的条件下才能达到此时间复杂度,例如用前向星或邻接表存储图;如果用邻接矩阵则不一定能达到此复杂度。

实现

用栈(Stack)为遍历中节点

vector<vector<int>> adj;  // 邻接表
vector<bool> vis;         // 记录节点是否已经遍历

void dfs(int s) {
  stack<int> st;
  st.push(s);
  vis[s] = true;

  while (!st.empty()) {
    int u = st.top();
    st.pop();

    for (int v : adj[u]) {
      if (!vis[v]) {
        vis[v] = true;  // 确保栈里没有重复元素
        st.push(v);
      }
    }
  }
}

递归实现

函数在递归调用时的求值如同对栈的添加和删除元素的顺序,故函数调用所占据的虚拟地址被称为函数调用栈(Call Stack),DFS 可用递归的方式实现。

以 邻接表(Adjacency List) 作为图的存储方式:

vector<vector<int>> adj;  // 邻接表
vector<bool> vis;         // 记录节点是否已经遍历

void dfs(const int u) {
  vis[u] = true;
  for (int v : adj[u])
    if (!vis[v]) dfs(v)
}

以 链式前向星 为例:

void dfs(int u) {
  vis[u] = 1;
  for (int i = head[u]; i; i = e[i].x) {
    if (!vis[e[i].t]) {
      dfs(v);
    }
  }
}

DFS 序列

DFS 序列是指 DFS 调用过程中访问的节点编号的序列。

我们发现,每个子树都对应 DFS 序列中的连续一段(一段区间)。

括号序列

DFS 进入某个节点的时候记录一个左括号 (,退出某个节点的时候记录一个右括号 )。

每个节点会出现两次。相邻两个节点的深度相差 \(1\)。

一般图上 DFS

对于非连通图,只能访问到起点所在的连通分量。

对于连通图,DFS 序列通常不唯一。

注:树的 DFS 序列也是不唯一的。

在 DFS 过程中,通过记录每个节点从哪个点访问而来,可以建立一个树结构,称为 DFS 树。DFS 树是原图的一个生成树。


BFS

是图上最基础、最重要的搜索算法之一。

所谓宽度优先。就是每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。

这样做的结果是,BFS 算法找到的路径是从起点开始的 最短 合法路径。换言之,这条路径所包含的边数最小。

在 BFS 结束时,每个节点都是通过从起点到该点的最短路径访问的。

算法过程可以看做是图上火苗传播的过程:最开始只有起点着火了,在每一时刻,有火的节点都向它相邻的所有节点传播火苗。

实现

void bfs(int u) {
  while (!Q.empty()) Q.pop();
  Q.push(u);
  vis[u] = 1;
  d[u] = 0;
  p[u] = -1;
  while (!Q.empty()) {
    u = Q.front();
    Q.pop();
    for (int i = head[u]; i; i = e[i].nxt) {
      if (!vis[e[i].to]) {
        Q.push(e[i].to);
        vis[e[i].to] = 1;
        d[e[i].to] = d[u] + 1;
        p[e[i].to] = u;
      }
    }
  }
}

void restore(int x) {
  vector<int> res;
  for (int v = x; v != -1; v = p[v]) {
    res.push_back(v);
  }
  std::reverse(res.begin(), res.end());
  for (int i = 0; i < res.size(); ++i) printf("%d", res[i]);
  puts("");
}

具体来说,我们用一个队列 \(Q\) 来记录要处理的节点,然后开一个布尔数组 vis[] 来标记是否已经访问过某个节点。

开始的时候,我们将所有节点的 \(vis\) 值设为 \(0\),表示没有访问过;然后把起点 \(s\) 放入队列 \(Q\) 中并将 vis[s] 设为 \(1\)。

之后,我们每次从队列 \(Q\) 中取出队首的节点 \(u\),然后把与 \(u\) 相邻的所有节点 \(v\) 标记为已访问过并放入队列 \(Q\)。

循环直至当队列 \(Q\) 为空,表示 BFS 结束。

在 BFS 的过程中,也可以记录一些额外的信息。例如上述代码中,\(d\) 数组用于记录起点到某个节点的最短距离(要经过的最少边数),\(p\) 数组记录是从哪个节点走到当前节点的。

有了 \(d\) 数组,可以方便地得到起点到一个节点的距离。

有了 \(p\) 数组,可以方便地还原出起点到一个点的最短路径。上述代码中的 restore 函数使用该数组依次输出从起点到节点 \(x\) 的最短路径所经过的节点。

时间复杂度 \(O(n + m)\)

空间复杂度 \(O(n)\)($vis4 数组和队列)

应用

在一个无权图上求从起点到其他所有点的最短路径。

在 \(O(n+m)\) 时间内求出所有连通块。(我们只需要从每个没有被访问过的节点开始做 BFS,显然每次 BFS 会走完一个连通块)

如果把一个游戏的动作看做是状态图上的一条边(一个转移),那么 BFS 可以用来找到在游戏中从一个状态到达另一个状态所需要的最小步骤。

在一个有向无权图中找最小环。(从每个点开始 BFS,在我们即将抵达一个之前访问过的点开始的时候,就知道遇到了一个环。图的最小环是每次 BFS 得到的最小环的平均值。)

找到可能在 \((a, b)\) 最短路上的边。(分别从 \(a\) 和 \(b\) 进行 BFS,得到两个 \(d\) 数组。之后对每一条边 \((u, v)\),如果 d_a[u]+1+d_b[v]=d_a[b],则说明该边在最短路上)

找到可能在 \((a, b)\) 最短路上的点。(分别从 \(a\) 和 \(b\) 进行 BFS,得到两个 \(d\) 数组。之后对每一个点 \(v\),如果 d_a[v]+d_b[v]=d_a[b],则说明该点在某条最短路上)

找到一条长度为偶数的最短路。(我们需要一个构造一个新图,把每个点拆成两个新点,原图的边 \((u, v)\) 变成 \(((u, 0), (v, 1))\) 和 \(((u, 1), (v, 0))\)。对新图做 BFS,\((s, 0)\) 和 \((t, 0)\) 之间的最短路即为所求)

在一个边权为 \(0/1\) 的图上求最短路,见下方双端队列 BFS。


双向搜索

折半搜索、meet in the middle

过程

Meet in the middle 算法的主要思想是将整个搜索过程分成两半,分别搜索,最后将两半的结果合并。

性质

暴力搜索的复杂度往往是指数级的,而改用 meet in the middle 算法后复杂度的指数可以减半
即让复杂度从 \(O(a^b)\) 降到 \(O(a^{b/2})\)。

还是 N 皇后

思路:搜索,记录当前行数,哪些列被放了,哪些斜对角线被放了。

#include <iostream>
#include <queue>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstring>
#include <cstdio>
#include <set>
#include <map>
#include <unordered_map>
#include <bitset>
using namespace std;
const int maxn = 1e4 + 10, MAXN = 1e9 + 10;
int row_valid[maxn], n, ans =0;
char a[maxn][maxn];
void dfs(int row, int col_ban, int diag_ban_l, int diag_ban_r){
	if(row == n){
		ans++;
		return;
	}
	int tmp_valid = row_valid[row] & ~ (col_ban | diag_ban_l | diag_ban_r);
	for(int i = 0;i < n; i++){
		if(tmp_valid >> i & 1){
			dfs(row + 1, col_ban | (1 << i), 
			(diag_ban_l | (1 << i)) << 1,
			(diag_ban_r | (1 << i)) >> 1);
		}
	}
}
int main(){
	cin >> n;
	for(int i = 0;i < n; i++){
		cin >> a[i];
		for(int j = 0;j < n; j++){
			if(a[i][j] == '*'){
				row_valid[i] |= 1 << j;
			}
		}
	}
	dfs(0, 0, 0, 0);
	cout<< ans << endl;
	return 0;
}

标签:24,int,day5,DFS,节点,BFS,vis,include,集训
From: https://www.cnblogs.com/yantaiyzy2024/p/18340817

相关文章

  • 24暑假集训day5上午
    图论差分约束有\(......
  • 24暑假集训day4上午&下午
    基础图论图的存储方式无向边可以拆成两条有向边1.邻接矩阵邻接矩阵:若\(......
  • Day 8.2 NOIP2024 模拟赛 总结
    Day8.2NOIP模拟赛总结T1T1赛时打表输出发现了等差数列的性质(好像不需要打表也能知道),然后我码完T2过后剩不到2个小时了,于是连T3T4暴力都没码就过来推了,但也没推出来,时间倒是耽误了不少,剩一个小时的时候去开始去码后面的暴力了。T2水题一道,做法,性质全给了。只不过比较玄学的......
  • 20240803题解
    话说T3都把式子推出来了结果忘记差分约束怎么写了。光线(light)题面:有\(n\)个玻璃板,第\(i\)个玻璃板的透光率为\(a_i\%\),反射率为\(b_i\%\),有大小为\(1\)个单位的一束光从第\(1\)个玻璃板开始,有多少光能穿透\(n\)层玻璃板。题解:考虑\(n=2\)时,可以简单算出两个玻璃板组合后的反......
  • 【笔记】动态规划选讲:凸优化技术大赏 2024.8.3
    如果您是搜索引擎搜进来的。很抱歉,没有您需要搜索的题目的题解。典题\(n\)个物品的背包,重量在\(1\sim4\)之间,价值在\(1\sim10^9\)之间。\(n\leq10^5\)。Minkowski和会遇到不连续的问题。不妨按照\(i\bmod12\)划分dp数组,每个剩余系都是凸的。枚举拿了\(......
  • IPC-6012F-CN-中文版\英文版,2024 刚性印制板的鉴定及性能规范
    IPC-6012F-CN-中文版,2024刚性印制板的鉴定及性能规范链接:https://pan.baidu.com/s/1z1x5JPmcRHzeIQgMsMQRxg提取码:1234https://share.weiyun.com/s7XNX9gE 2023年10月,IPC-6012发布了最新版F版。与以往版本不同的是,F版中国成立了分技术组,收集,讨论和提交了大量制修订的意......
  • Day 8.1 NOIP2024 模拟赛 总结
    ​Day8.1NOIP2024模拟赛总结T1开赛后首先是码了本题的暴力,想了想之后只是感觉这个结构很像二叉树,然后没有细想,想着先码完后面的暴力再回来。T2Subtask2就是简单推性质,优化一下循环枚举顺序就可以了。当时想Subtask1的时候,本身是考虑枚举每一个点然后暴力向外拓展,时间......
  • docker安装zabbix 20240803
    宿主机IP:192.168.177.1281、下载数据库:dockerpullmysql:5.7 2、下载支持数据库的zabbix:dockerpullzabbix/zabbix-server-mysql:centos-latest 3、下载web容器:dockerpullzabbix/zabbix-web-nginx-mysql:latest  4、下载java监控:dockerpullzabbix/z......
  • 2024牛客多校6
    第五场太抽象了,失去补题欲望6ACake(A)首先假设字符串已经确定,对Oscar而言,由于一份蛋糕可以为空,在两人都尽可能取最大值的情况下,相当于忽略所有空的部分、只根据字符串的某个前缀\(s'\)划分蛋糕,按照字符串中0占比最大的前缀平均划分一定是最优的。回到游戏第一步,已知Oscar的......
  • 2024年暑假关于线段树和树状数组的小知识点
    1.线段树的树形结构使得存储其的数组应开4N,其中N为元素个数2.多用宏定义使代码更简单3.树状数组求逆序对一般会写成add(a[i],1);quiry(a[i]-1);这会导致当元素值域包含0时传入-1导致死循环,可以在quiry函数判断合法性;一种比较好的写法是干脆add时add(a[i]+1,1),然后直接查......