Coursera 算法基础,北京大学

行的状态就均可从上一行推出
若最后一行的灯成功熄灭,证明该方案可行
2. 讨厌的青蛙
两点确定一条直线:枚举直线的起始两点确定直线
适当剪枝


递归

  1. 小游戏
  2. 棋盘分割
  3. 用栈模拟递归
  4. 完美覆盖:
    需要仔细思考的一道 DP,我们可以发现对于一个矩形的完美覆盖可以有两种
    一种可以将覆盖(竖着)分成两个子矩形,另一种则不能分割
    为了不算重复情况,我们规定新增的矩形覆盖一定是不能分割的
    观察到当 \(n=2\) 时,不能分割的覆盖情况有 \(3\)
    而当 \(n>2\) 时则只有两种 (第 \(1\)\(2\) 行或第 \(2\)\(3\) 行交错)
    基于此可以写出转移方程
#include <iostream>

using namespace std;

int dp[50];

int main() {

    dp[0] = 1, dp[2] = 3;
    for (int i = 4; i <= 30; i += 2) {
        dp[i] += 3 * dp[i - 2];
        for (int j = i - 4; j >= 0; j -= 2) 
            dp[i] += 2 * dp[j];
    }

    int n;
    while (cin >> n) {
        if (n == -1)    break;
        cout << dp[n] << endl;
    }

    return 0;
}
  1. 文件目录
    栈模拟递归即可

动态规划

下限很低,上限巨高的算法
两个特点:最优子问题;无后效性

  1. 最长公共子序列 (LCS)
    \(f[x][y]\) 一序列 \(1\)\(x\) 二序列 \(1\)\(y\) 的最长公共子序列的长度
    复杂度 \(O(nm)\)
  2. 最长上升子序列
    \(f[x]\) 以第 \(x\) 个数位结尾的最长上升子序列长度
    复杂度 \(O(n^2)\)
  3. "人人为我" 与 "我为人人" 型递推:"人人为我"型有时可以采取数据结构优化
  4. 灌溉操场 (设计状态有点难)
  5. 方盒游戏
    \(f[x][y][exlen]\) 消除第 \(x\) 个大块到第 \(y\) 个大块且右边只剩一个长度为 \(exlen\) 且与 \(y\) 同色的大块能得到的最高得分
    \(f[x][y][exlen] = max(f[x][k][exlen + len(y)] + f[k + 1][y - 1][0], f[x][y - 1][0] + (len(y)+exlen)^2)\)
    答案为 \(f[1][n][0]\)
  6. 美丽栅栏
    定义交叉序列为:每个数都同时大于或小于其相邻数的序列称为交叉序列
    \(n\) 个数,求按字典序排列的第 \(c\) 个交叉序列
    \(f[n][k][up/down]\) 代表有 \(n\) 个数,以第 \(k\) 小的数打头,且开头呈上升/下降趋势的交叉序列方案数
    \(f[n][k][up] = \sum f[n - 1][k...n-1][down]\)
    \(f[n][k][down] = \sum f[n - 1][1...k-1][up]\)
    \(f[1][1][up]=f[1][1][down] = 1\)
    计算出 \(f\) 数组后,求第 \(c\) 个排列即用枚举试错法即可

深度优先搜索 DFS

  1. 拯救少林神棍
    之前也做过,有贼多剪枝,说实话自己想不出来
  2. 剪枝:可行性剪枝,最优化剪枝
  3. 做蛋糕
    计算边界来进行可行性剪枝
  4. 碎纸机
    简单深搜,状态和剪枝都很明确

广度优先搜索 BFS

  1. 八数码问题
    很经典的广搜问题,之前也做过,但是这次听很有启发,关于保存八数码的状态
    之前直接用 \(map\) 暴力存状态过的,这样虽然方便,但是颇有些名不正言不顺之感
    如果以字符串形式保存八数码,占太多空间,难以接受 (除非用 \(map\),这样无用的状态不会占用空间)
    将八数码视为排列,将每一种方案映射成排列中的序数
    \(0123456789\) 映射为 \(0\) 因为它是第 \(0\) 个排列,这样就实现了状态的保存 (感觉有一点离散化的思想在里面)
  2. 八数码问题解的判定
    八数码问题的状态实际上是 \(0\)\(8\) 的一个排列,对于任意初始状态到目标状态,不一定存在解路径
    因为排列有奇排列与偶排列两类,可以确定奇排列不能转化为偶排列,偶排列不能转化为奇排列
    对于一个随机排列,定义 \(f(x) (x \neq 0)\) 为数字 \(x\) 之前比它小的数的个数。全部数字的 \(f(x)\) 之和定义为 \(y = \sum f(x)\)
    \(y\) 为奇数,则排列为奇排列;反之为偶排列
  3. 双向广搜 \(DBFS\)
    从起始节点与目标节点以广度优先顺序同时扩展,扩展队列相交后可视为找到一条解路径
    \(BFS\) 相比,搜索树宽度明显减小
    在扩展时,总是选择扩展结点数量较少一侧进行扩展,以保证两个扩展队列的大小相仿
  4. \(A*\) 算法
  5. 还有一个操作以前没用过:广搜时存储每一个节点的父节点指针,由此在达到目标状态之后可以输出解路径
  6. 广搜 vs 深搜
    广搜:对于状态简单,空间充足的求最优解问题有优势(完备策略)
    深搜:几乎适合一切问题,在特定问题上时间逊于广搜
  7. \(Flip Game\)
    因为一个大小写 \(WA\) 了我一个小时...
    后来看到题解有更加快速且简介的做法:由于 \(n=4\), 共有 \(4 \times 4 = 16\)\(0/1\),完全可以二进制压缩存储
    翻棋子即可视为与某一个二进制数异或:需要翻转的位设为 \(1\) ,不需要翻转的设为 \(0\)

二分与贪心

  1. 雷达安装问题
    对这个题很有印象,以前绝对做过(果然,在洛谷上找到了 P1325)
    将每个小岛对应的的雷达安装范围作为线段储存,问题转化为经典的线段覆盖问题:
    数轴上给出若干线段,选择最少的 \(n\) 个点使得每一根线段都包含至少一个点
    贪心策略很容易理解:将线段按照左端点排序并维护一个线段集合,记录该集合的最左 右端点
    可以发现,这个最左右端点可以覆盖此集合内的所有线段
    若新添加的线段左端点大于线段集合的最左右端点,说明之前的点已经无法覆盖到当前这条线段:那么重新创立一个线段集合
    否则将其加入原有的线段集合,并更新最左右端点
  2. 补充另外一个线段覆盖问题:
    数轴上给出若干线段,要求选择其中 \(k\) 条两两不相交的线段,使得 \(k\) 最大
    贪心策略:放右端点最靠左的线段最好,从左向右放,右端点越小妨碍越少
    其他线段放置按右端点排序,贪心放置线段,即能放就放
  3. Gone fishing
    写的 \(DP\) 过了样例,不知道为什么 \(WA\) 了,而且网上搜的标程也 \(WA\)
    看了题解,这个贪心确实很妙
    枚举目的地湖,并将时间减去移动需要的时间,这样可以视为能在任意湖之间 "瞬间移动"
    然后在这些湖当中贪心的选择:哪个湖鱼多就选哪个湖,直至时间耗尽:若鱼钓完之后还有时间,则全部分配给第一个湖
posted @ 2022-05-03 17:27  四季夏目天下第一  阅读(22)  评论(0编辑  收藏  举报