挑战程序设计竞赛
初出茅庐——初级篇
搜索
DFS
从某个状态开始,不断地转移状态直至无法转移,然后回退到前一步的状态,继续转移到其他状态,如此不断重复,直至找到最终的解。
部分和
选出若干数,和恰好为k。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 输入
int a[MAX_N]; int n, k;
// 已经从前i项得到了和sum,然后对于i项之后的进行分支
bool dfs(int i, int sum) {
// 如果前n项都计算过了,则返回sum是否与k相等
if (i == n) return sum == k;
// 不加上a[i]的情况
if (dfs(i + 1, sum)) return true;
// 加上a[i]的情况
if (dfs(i + 1, sum + a[i])) return true;
// 无论是否加上a[i]都不能凑成k就返回false
return false;
}
void solve() {
if (dfs(0, 0)) printf("Yes\n");
else printf("No\n");
}Lake Counting (DFS一次就是消掉了一块积水)
从任意的W开始,不停地把邻接的部分用’.’代替。1次DFS后与初始的这个W连接的所有W就都被替 换成了’.’,因此直到图中不再存在W为止,总共进行DFS的次数就是答案了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40// 输入
int N, M;
char field[MAX_N][MAX_M + 1]; // 园子
// 现在位置(x,y)
void dfs(int x, int y) {
// 将现在所在位置替换为.
field[x][y] = '.';
// 循环遍历移动的8个方向
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
// 向x方向移动dx,向y方向移动dy,移动的结果为(nx,ny)
int nx = x + dx, ny = y + dy;
// 判断(nx,ny)是不是在园子内,以及是否有积水
if (0 <= nx && nx < N && 0 <= ny && ny < M && field[nx][ny] == 'W')
dfs(nx, ny);
}
}
return ;
}
void solve() {
int res = 0;
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
{
if (field[i][j] == 'W')
{
// 从有W的地方开始dfs
dfs(i, j);
res++;
}
}
}
printf("%d\n", res);
}
BFS
与深度优先搜索的不同之处在于搜索的顺序,宽度优先搜索总是先搜索距离初始状态近的状态。 也就是说,它是按照开始状态→只需1次转移就可以到达的所有状态→只需2次转移就可以到达的所有状态→……
深度优先用栈,宽度优先用队列
迷宫最短路径
宽度优先搜索按照距开始状态由近及远的顺序进行搜索,因此可以很容易地用来求最短路径、最少操作之类问题的答案。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58const int INF = 100000000;
// 使用pair表示状态时,使用typedef会更加方便一些
typedef pair<int, int> P;
// 输入
char maze[MAX_N][MAX_M + 1]; // 表示迷宫的字符串的数组
int N, M;
int sx, sy; // 起点坐标
int gx, gy; // 终点坐标
int d[MAX_N][MAX_M]; // 到各个位置的最短距离的数组
// 4个方向移动的向量
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
// 求从(sx, sy)到(gx, gy)的最短距离
// 如果无法到达,则是INF
int bfs() {
queue<P> que;
// 把所有的位置都初始化为INF
for (int i = 0; i < N; i++)
for (int j = 0; j < M; j++)
d[i][j] = INF;
// 将起点加入队列,并把这一地点的距离设置为0
que.push(P(sx, sy));
d[sx][sy] = 0;
// 不断循环直到队列的长度为0
while (que.size()) {
// 从队列的最前端取出元素
P p = que.front(); que.pop();
// 如果取出的状态已经是终点,则结束搜索
if (p.first == gx && p.second == gy) break;
// 四个方向的循环
for (int i = 0; i < 4; i++) {
// 移动之后的位置记为(nx, ny)
int nx = p.first + dx[i], ny = p.second + dy[i];
// 判断是否可以移动以及是否已经访问过(d[nx][ny]!=INF即为已经访问过)
if (0 <= nx && nx < N && 0 <= ny && ny < M && maze[nx][ny] != '#' && d[nx][ny] == INF) {
// 可以移动的话,则加入到队列,并且到该位置的距离确定为到p的距离+1
que.push(P(nx, ny));
d[nx][ny] = d[p.first][p.second] + 1;
}
}
}
return d[gx][gy];
}
void solve() {
int res = bfs();
printf("%d\n", res);
}
贪心
硬币问题
优先使用面值大的硬币
1
2
3
4
5
6
7
8……
for(int i = 5; i >= 0; i--)
{
int t = min(A/V[i],C[i]);
A-= t*V[i];
ans+=t;
}
……区间调度问题
在可选的工作中,每次都去结束时间最早的工作
Fence Repair
木板的长度*节点的深度 ==> 哈夫曼编码
动态规划
01背包
$memset(dp,-1,sizeof(dp))$ 按照1字节为单位对内存进行填充,-1每个二进制都为1。memset只能初始化为0或-1。
状态 $dp[i][j]$ :从第i个物品开始挑选总重小于j的部分
递推
$dp[n][j] = 0$
$dp[i][j] = max(dp[i+1][j],dp[i+1][j-w[i]]+v[i])$
完全背包
从01背包的选或不选转化为选多少件。
多重部分和
状态:$dp[i+1][j]$: 用前i种数加和得到j时第i种数最多剩余多少个(不能加和得到i的情况为-1)
数据结构
优先队列和堆
堆得性质是儿子的值一定不小于父亲的值。
- 插入:首先在堆的末尾插入该数值,然后不断向上提升直到没有大小颠倒为止
- 删除:首先把堆得最后一个节点的数值复制到根节点上,删除最后一个节点,不断向下交换直到没有大小颠倒为止
Expedition
trick:在到达加油站$i$时,就获得了一次在之后的任何时候都可以加$B_i$单位汽油的权利
- 在经过加油站时,往优先队列里加入$B_i$
- 当燃料空了时
- 如果优先队列也是空的,则无法到达终点
- 否则取出优先队列中的最大元素,并用来给卡车加油
二叉搜索树
删除
- 需要删除的节点没有左儿子,就把右儿子提上去
- 需要删除的节点的左儿子没有右儿子,把左儿子提上去
- 以上都不满足,就把左儿子子孙中最大的节点提到需要删除的节点上
图
二分图判定
dfs.
从任意一个点出发,依次确定相邻顶点的颜色
最短路径
Bellman-Ford算法
$d[i] = min{d[j] + (j到i的边的权值)|e=(j,i)\in E }$
Dijkstra算法
- 找到最短距离已经确定的顶点,从它出发更新相邻顶点的最短距离
- 此后不需要再关心1中的“最短距离已经确定的顶点”
1 | struct edge {int to, cost;}; |
Floyd-Warshall算法
任意两点最短路径
最小生成树
Prim算法
Prim算法和Dijkstra算法十分相似,都是从某个顶点出发,不断添加边的算法。
- 一颗只包含一个顶点v的树T
- 贪心地选取T和其他顶点之间相连的最小权值的边,并把它加入到T中
Kruskal算法
- 按照权值的顺序从小到大查看一遍
- 如果不产生圈,就把当前这条边加入到生成树中
- 判断是否产生圈:如果加入之前$u$和$v$不在同一连通分量里,那么加入也不会产生圈,可以使用并查集高效地判断是否属于同一个连通分量。
数学问题
辗转相除法
1 | //a>b |
素数判定
O($\sqrt n$)
素数的个数
埃氏筛法
- 首先,将2到范围n的所有整数写下来
- 其中最小的数字是2,将表中2的倍数都划去
- 再将表中所有3的倍数都划去
- ……
- 如果表中剩余的最小数字是m,m就是素数,将表中所有m的倍数都划去