时间复杂度介绍
一般ACM或者笔试题的时间限制是1秒或2秒。
在这种情况下,C++代码中的操作次数控制在 1 0 7 ∼ 1 0 8 10^7∼10^8 107∼108 为最佳。
下面给出在不同数据范围下,代码的时间复杂度和算法该如何选择:
- n ≤ 30 n≤30 n≤30,
指数级别, dfs+剪枝,状态压缩dp - n ≤ 100 n≤100 n≤100 => O ( n 3 ) O(n^3) O(n3),
floyd,dp,高斯消元 - n ≤ 1000 n≤1000 n≤1000 => O ( n 2 ) O(n^2) O(n2), O ( n 2 l o g n ) O(n^2logn) O(n2logn),
dp,二分,朴素版Dijkstra、朴素版Prim、Bellman-Ford - n ≤ 10000 n≤10000 n≤10000 => O ( n ∗ n ) O(n∗\sqrt{n}) O(n∗n),
块状链表、分块、莫队 - n ≤ 100000 n≤100000 n≤100000 => O ( n l o g n ) O(nlogn) O(nlogn) =>
各种sort,线段树、树状数组、set/map、heap、拓扑排序、dijkstra+heap、prim+heap、Kruskal、spfa、求凸包、求半平面交、二分、CDQ分治、整体二分、后缀数组、树链剖分、动态树 - n ≤ 1000000 n≤1000000 n≤1000000 => O ( n ) O(n) O(n), 以及常数较小的 O ( n l o g n ) O(nlogn) O(nlogn) 算法 =>
单调队列、 hash、双指针扫描、并查集,kmp、AC自动机,常数比较小的 O ( n l o g n ) O(nlogn) O(nlogn) 的做法:sort、树状数组、heap、dijkstra、spfa - n≤10000000n≤10000000 => O(n)O(n),
双指针扫描、kmp、AC自动机、线性筛素数 - n ≤ 1 0 9 n≤10^9 n≤109 => O ( n ) O(\sqrt{n}) O(n),
判断质数 - n ≤ 1 0 18 n≤10^{18} n≤1018 => O ( l o g n ) O(logn) O(logn),
最大公约数,快速幂,数位DP - n ≤ 1 0 1000 n≤10^{1000} n≤101000 => O ( ( l o g n ) 2 ) O((logn)^2) O((logn)2),
高精度加减乘除 - n ≤ 1 0 100000 n≤10^{100000} n≤10100000 => O ( l o g k × l o g l o g k ) O(logk×loglogk) O(logk×loglogk),
k表示位数,高精度加减、FFT/NTT int数据范围:-2147483648~2147483647、大约 2 ∗ 1 0 9 2*10^9 2∗109左右long long数据范围:正负 1 0 18 10^{18} 1018之间- 输入的数据范围 < 1 0 5 <10^5 <105,可以用
cin、cout - 输入的数据范围 ≥ 1 0 5 ≥10^5 ≥105,建议用
scanf、printf - 2 16 ≈ 1 0 6 2^{16}≈10^6 216≈106、 2 16 = 65536 2^{16}=65536 216=65536、 2 63 ≈ 1 0 18 2^{63}≈10^{18} 263≈1018
前言
队列,bfs。栈,dfs,递归。
《深入理解计算机系统》
bfs: 数据结构:queue、空间:O(2^h)空间大、可以找到一条合法路径,最小步数,一层一层进行拓展,最短路。
dfs: 数据结构:stack、空间:O(n)空间小、不能找最短路径,不具有最短路性质
思路奇怪的:dfs
最短距离:bfs
技巧:
4方向偏移量:
int dx[4] = {
-1, 0, 1, 0}, dy[4] = {
0, 1, 0, -1};
8方向偏移量:
int dx[8] = {
-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {
-1, 0, 1, 1, 1, 0, -1, -1};
8方向日字形偏移量:
int dx[8] = {
-2, -1, 1, 2, 2, 1, -1, -2};
int dy[8] = {
1, 2, 2, 1, -1, -2, -2, -1};
注意: 对于多组输入的题目,不要忘记将判重数组初始化memset
深搜注意爆栈
深搜恢复现场:一个状态需要多次使用时才会恢复现场。
一、深搜
回溯、剪枝
1.1 深搜思想
关键:考虑顺序,遍历全部方案。
回溯时恢复现场st[]数组
path[]存路径
Flood Fill、图与树的遍历、
深搜的返回条件
//迷宫问题:是否走到最后一个点
if(x==xb&&y==yb) return true;
//其他类搜索问题:是否搜索到最后一个点
if(cnt==n*m) {
ans++;return;}
//搜索到没有解
cnt+=dfs(a,b);
1.2 基础题目
1.2.1 排列数字


假设有 3 个空位,从前往后填数字,每次填一个位置,填的数字不能和前面一样。
最开始的时候,三个空位都是空的:__ __ __
首先填写第一个空位,第一个空位可以填 1,填写后为:1 __ __
填好第一个空位,填第二个空位,第二个空位可以填 2,填写后为:1 2 __
填好第二个空位,填第三个空位,第三个空位可以填 3,填写后为: 1 2 3
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:1 2 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 3 ,没有其他数字可以填。
因此再往后退一步,退到了状态:1 __ __。第二个空位上除了填过的 2,还可以填 3。第二个空位上填写 3,填写后为:1 3 __
填好第二个空位,填第三个空位,第三个空位可以填 2,填写后为: 1 3 2
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:1 3 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 2,没有其他数字可以填。
因此再往后退一步,退到了状态:1 __ __。第二个空位上除了填过的 2,3,没有其他数字可以填。
因此再往后退一步,退到了状态:__ __ __。第一个空位上除了填过的 1,还可以填 2。第一个空位上填写 2,填写后为:2 __ __
填好第一个空位,填第二个空位,第二个空位可以填 1,填写后为:2 1 __
填好第二个空位,填第三个空位,第三个空位可以填 3,填写后为:2 1 3
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:2 1 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 3,没有其他数字可以填。
因此再往后退一步,退到了状态:2 __ __。第二个空位上除了填过的 1,还可以填 3。第二个空位上填写 3,填写后为:2 3 __
填好第二个空位,填第三个空位,第三个空位可以填 1,填写后为:2 3 1
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:2 3 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 1,没有其他数字可以填。
因此再往后退一步,退到了状态:2 __ __。第二个空位上除了填过的 1,3,没有其他数字可以填。
因此再往后退一步,退到了状态:__ __ __。第一个空位上除了填过的 1,2,还可以填 3。第一个空位上填写 3,填写后为:3 __ __
填好第一个空位,填第二个空位,第二个空位可以填 1,填写后为:3 1 __
填好第二个空位,填第三个空位,第三个空位可以填 2,填写后为:3 1 2
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:3 1 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 2,没有其他数字可以填。
因此再往后退一步,退到了状态:3 __ __。第二个空位上除了填过的 1,还可以填 2。第二个空位上填写 2,填写后为:3 2 __
填好第二个空位,填第三个空位,第三个空位可以填 1,填写后为:3 2 1
这时候,空位填完,无法继续填数,所以这是一种方案,输出。
然后往后退一步,退到了状态:3 2 __ 。剩余第三个空位没有填数。第三个空位上除了填过的 1,2,没有其他数字可以填。
因此再往后退一步,退到了状态:3 __ __。第二个空位上除了填过的 1,2,没有其他数字可以填。
因此再往后退一步,退到了状态:__ __ __。第一个空位上除了填过的 1,2,3,没有其他数字可以填。
此时深度优先搜索结束,输出了所有的方案。

算法:
- 用 path 数组保存排列,当排列的长度为 n 时,是一种方案,输出。
- 用 state 数组表示数字是否用过。当 state[i] 为 1 时:i 已经被用过,state[i] 为 0 时,i 没有被用过。
- dfs(i) 表示的含义是:在 path[i] 处填写数字,然后递归的在下一个位置填写数字。
- 回溯:第 i 个位置填写某个数字的所有情况都遍历后, 第 i 个位置填写下一个数字。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10;
int n;
int path[N]; // 从0到n-1共n个位置 存放一个排列
bool st[N];// 存放每个数字的使用状态 true表示使用了 false表示没使用过
void dfs(int u)
{
if(u==n)//数字填完了,输出
{
for(int i=0;i<n;i++)
printf("%d ",path[i]);
cout << endl;
return;
}
for(int i=1;i<=n;i++)//空位上可以选择的数字为:1 ~ n
{
if(!st[i])//如果数字 i 没有被用过
{
path[u]=i;// 把 i 填入数字排列的位置上
st[i]=true;// 表示该数字用过了 不能再用
dfs(u+1);// 填下一位
st[i]=false;// 恢复现场 该数字后续可用
}
}
}
int main()
{
cin >> n;
dfs(0);
return 0;
}
1.2.2 n-皇后问题




DFS按行枚举
对角线 dg[u+i],反对角线udg[n−u+i]中的下标 u+i和 n−u+i表示的是截距
下面分析中的(x,y)相当于上面的(u,i)
- 反对角线
y=x+b, 截距b=y−x,因为我们要把b当做数组下标来用,显然 b 不能是负的,所以我们加上+n(实际上+n+4,+2n都行),来保证是结果是正的,即y - x + n - 对角线
y=−x+b, 截距是b=y+x,这里截距一定是正的,所以不需要加偏移量
核心目的: 找一些合法的下标来表示dg或udg是否被标记过,所以如果你愿意,你取 udg[n+n−u+i] 也可以,只要所有(u,i)对可以映射过去就行
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
int n;
char g[N][N];
int col[N],dg[N],udg[N];
void dfs(int u)
{
if(u==n)
{
for(int i=0;i<n;i++)
puts(g[i]);
puts("");
}
for(int i=0;i<n;i++)
{
if(!col[i]&&!dg[i+u]&&!udg[i-u+n])
{
g[u][i]='Q';
col[i]=dg[i+u]=udg[i-u+n]=true;
dfs(u+1);
col[i]=dg[u+i]=udg[n-u+i]=false;
g[u][i]='.';
}
}
}
int main()
{
cin >> n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
g[i][j]='.';
dfs(0);
return 0;
}
DFS按每个元素枚举
每个位置都有两种情况,总共有 n^2 个位置
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20;
char g[N][N];
int col[N],row[N],dg[N],udg[N];// 因为是一个个搜索,所以加了row
int n;
void dfs(int x,int y,int s)// s表示已经放上去的皇后个数
{
if(y==n) x++,y=0;// 处理超出边界的情况
if(x==n)// x==n说明已经枚举完n^2个位置了
{
if(s==n)// s==n说明成功放上去了n个皇后
{
for(int i=0;i<n;i++) puts(g[i]);
puts("");
}
return;
}
//不放皇后
dfs(x,y+1,s);
//放皇后
if(!col[x]&&!row[y]&&!dg[y-x+n]&&!udg[y+x])
{
g[x][y]='Q';
col[x]=row[y]=dg[y-x+n]=udg[y+x]=true;
dfs(x,y+1,s+1);
col[x]=row[y]=dg[y-x+n]=udg[y+x]=false;
g[x][y]='.';
}
}
int main()
{
cin >> n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
g[i][j]='.';
dfs(0,0,0);
return 0;
}
1.3 DFS中的连通性(能走到,不能保证最短)DFS、BFS均可以求解
1.3.1 迷宫


算法思想:
只能保证搜到,不能保证最短。
相对BFS,DFS(用系统栈)代码短一些。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
char g[N][N];
bool st[N][N];
int k,n;
int xa,ya,xb,yb;
bool dfs(int x,int y)
{
if(g[x][y]=='#') return false;
if(x==xb&&y==yb) return true;
st[x][y]=true;
int dx[4]

本文详细介绍了深度优先搜索(DFS)和宽度优先搜索(BFS)在排列数字、N皇后问题、迷宫、红黑树等经典问题中的实践,以及递推和递归在解决指数型、排列型和组合型问题中的运用。还涵盖了最短路模型、多源BFS、A*算法等高级搜索策略。

9256

被折叠的 条评论
为什么被折叠?



