CSP认证准备第三天-差分及第36次CCF认证(BFS)
基础知识参考:
csp突击前两题常用算法代码_ccf csp常用优化算法-CSDN博客
差分
什么是差分数组?
差分数组是原数组相邻元素之间的差值构成的数组。对于原数组 a
,其差分数组 b
定义为:
-
b[1] = a[1]
(假设a[0] = 0
) -
b[i] = a[i] - a[i-1]
(对于 i > 1)
为什么差分数组有用?
差分数组的神奇之处在于:对原数组的区间加减操作可以转化为对差分数组的两个单点操作。
差分例题
一个长度为n的整数序列。
对其进行m次操作,每个操作包含三个整数l, r, c,表示将序列中[l, r]之间的每个数加上c。
请你输出进行完所有操作后的序列。###输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数序列。
接下来m行,每行包含三个整数l,r,c,表示一个操作。
###输出格式
共一行,包含n个整数,表示最终序列。
###数据范围
1≤n,m≤100000,
1≤l≤r≤n,
−1000≤c≤1000,
−1000≤整数序列中元素的值≤1000
如果用暴力做法每次都循环一遍区间的值然后操作的话时间肯定会超限
所以用一种巧妙地方法就是构造差分数组,
对区间[l,r]进行+c操作时只需要在差分数组里对b[l] += c,b[r+1] -= c
然后累和恢复数组时就可对区间内所有数+c
区间更新原理
当我们要对原数组 a
的区间 [l, r]
中每个元素加 c
时:
-
对差分数组
b[l] += c
:这使得a[l]
及之后的所有元素都增加了c
-
对差分数组
b[r+1] -= c
:这抵消了a[r+1]
及之后元素的增加,使得只有[l, r]
区间内的元素增加了c
恢复原数组
通过计算差分数组的前缀和,我们可以恢复出更新后的原数组:
-
a'[i] = b[1] + b[2] + ... + b[i]
或者下标直接从1开始比较好:
#include<iostream>
using namespace std;
int a[100010], b[100010];
int main(){int n, m;cin>>n>>m;for(int i = 1; i <= n; i ++){cin>>a[i];b[i] = a[i] - a[i - 1]; //构造差分数组}while(m --){int l, r, c;cin>>l>>r>>c;b[l] += c; b[r + 1] -= c;}for(int i = 1; i <= n; i ++){b[i] = b[i] + b[i - 1];cout<<b[i]<<" ";}return 0;
}
第36次CCF认证-第四题
参考题解:
CCF-CSP第36次认证第四题——跳房子【NA!巧妙利用BFS】_csp跳房子-CSDN博客
第36次ccf-csp题解(思维) - devoteeing - 博客园
应该属于经典题,BFS寻找最短路径,我还是不太熟悉,需要多多刷题啊。依稀记得当时有过短短的挣扎,然而确实是练少了,真的想不起来。(OK啊,后面我要系统练一下搜索算法了)
好吧,承认我读完题目之后真的毫无头绪,这种求最优解法的题目我真的束手无措。
BFS
参考资料:
BFS(图论) - OI Wiki
第十三章 DFS与BFS(保姆级教学!!超级详细的图示!!)_dfs bfs-CSDN博客
BFS 全称是 Breadth First Search,中文名是宽度优先搜索,也叫广度优先搜索。
是图上最基础、最重要的搜索算法之一。
所谓宽度优先。就是每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。
这样做的结果是,BFS 算法找到的路径是从起点开始的 最短 合法路径。换言之,这条路径所包含的边数最小。
在 BFS 结束时,每个节点都是通过从起点到该点的最短路径访问的。
算法过程可以看做是图上火苗传播的过程:最开始只有起点着火了,在每一时刻,有火的节点都向它相邻的所有节点传播火苗。
例题1-走迷宫
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N=110;
typedef pair<int,int> PII;
int map[N][N],mark[N][N];
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1},n,m,ans;
void bfs()
{memset(mark,-1,sizeof mark);queue<PII>q;q.push({0,0});mark[0][0]=0;while(!q.empty()){PII top=q.front();for(int i=0;i<4;i++){int nex=top.first+dx[i],ney=top.second+dy[i];if(nex>=0&&nex<n&&ney>=0&&ney<m&&mark[nex][ney]==-1&&map[nex][ney]==0){mark[nex][ney]=mark[top.first][top.second]+1;q.push({nex,ney});}}q.pop();}cout<<mark[n-1][m-1];
}
int main()
{cin>>n>>m;for(int i=0;i<n;i++){for(int j=0;j<m;j++){scanf("%d",&map[i][j]);}}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("");
}
其中,
图的存储方式(链式前向星)
head[u]
和 e[i]
是链式前向星(一种图的邻接表存储方式)的关键部分:
-
head[u]
:存储节点u
的第一条边的编号(索引)。 -
e[i]
:是一个结构体数组,存储第i
条边的信息,通常包括:-
e[i].to
:这条边指向的节点(终点)。 -
e[i].nxt
:下一条与u
相连的边的编号(类似于链表中的next
指针)
-
BFS应用
例题2-密室
问题 - 173B - Codeforces
一个n*m的图,现在有一束激光从左上角往右边射出,每遇到 '#',你可以选择光线往四个方向射出,或者什么都不做,问最少需要多少个 '#' 往四个方向射出才能使光线在第n行往右边射出。
此题目正解不是 0-1BFS,但是适用 0-1BFS,减小思维强度,赛时许多大佬都是这么做的。
做法很简单,一个方向射出不需要花费(0),而往四个方向射出需要花费(1),然后直接来就可以了。
#include <deque>
#include <iostream>
using namespace std;constexpr int INF = 1 << 29;
int n, m;
char grid[1001][1001];
int dist[1001][1001][4];
int fx[] = {1, -1, 0, 0};
int fy[] = {0, 0, 1, -1};
deque<int> q; // 双端队列void add_front(int x, int y, int dir, int d) { // 向前方加if (d < dist[x][y][dir]) {dist[x][y][dir] = d;q.push_front(dir);q.push_front(y);q.push_front(x);}
}void add_back(int x, int y, int dir, int d) { // 向后方加if (d < dist[x][y][dir]) {dist[x][y][dir] = d;q.push_back(x);q.push_back(y);q.push_back(dir);}
}int main() {cin >> n >> m;for (int i = 0; i < n; i++) cin >> grid[i];for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)for (int k = 0; k < 4; k++) dist[i][j][k] = INF;add_front(n - 1, m - 1, 3, 0);while (!q.empty()) { // 具体搜索的过程,可以参考上面写的题解int x = q[0], y = q[1], dir = q[2];q.pop_front();q.pop_front();q.pop_front();int d = dist[x][y][dir];int nx = x + fx[dir], ny = y + fy[dir];if (nx >= 0 && nx < n && ny >= 0 && ny < m)add_front(nx, ny, dir, d); // 判断条件if (grid[x][y] == '#')for (int i = 0; i < 4; i++)if (i != dir) add_back(x, y, i, d + 1);}if (dist[0][0][3] == INF)cout << -1 << endl;elsecout << dist[0][0][3] << endl;return 0;
}
习题
- 「NOIP2017」奶酪
双端队列 BFS:
- CF1063B. Labyrinth
- CF173B. Chamber of Secrets
- 「BalticOI 2011 Day1」打开灯泡 Switch the Lamp On