基础算法思想与搜索枚举

位运算

常用运算符

  • 按位与 &
  • 按位或 |
  • 按位异或 ^
  • 取反 ~
  • 左移 <<
  • 右移 >>
  • 非负整数原码反码补码都一样!
  • 运算符优先级不清楚就打括号!
  • C++运算符优先级

应用场景

  • 用二进制位表示元素的存在情况
  • 题目要求进行位运算
  • 获取二进制的某一位
int getBit(int a, int b) {
    return (a >> b) & 1;
}
  • 将一个数二进制某一位设为 0/1/取反
int unsetBit(int a, int b) {
    return a & ~(1 << b);
}
int setBit(int a, int b) {
    return a | (1 << b);
}
int flapBit(int a, int b) {
    return a * (1 << b);
}

std::bitset

  • 存储 \(0/1\) 的大小不可变的容器 bitset<SIZE> bs;
  • bs.count() 返回 \(1\) 的个数
  • bs.size() 返回 bs 的位数
  • bs.any() 返回是否有 \(1\)
  • bs.none() 返回是否全部为 \(0\)
  • bs.all() 返回是否全部为 \(1\)
  • bs.reset(x) x\(0\)\(1\),将 bs 中元素全部设为 x
  • operator[]==!=支持位运算!

位运算例题

搜索

暴力枚举所有方案(深度优先搜索)

void dfs(int n, ...) {
    if (所有状态均枚举完成) {
       ...
       return; // 一定要返回
    }
    for (遍历当前状态所有扩展可能性) {
        if (判断扩展状态是否合法) {
            调整状态
            dfs(n + 1, ...); // 向深层递归搜索
            复原状态(一定要回溯!!)
        }
    }
}

图上的深度优先遍历(用 vis 数组跳过已经走过的点)

void dfs(int u) { // 当前的位置(节点)
    vis[u] = 1;
    for (与 u 相邻的节点 v) {
        if (!vis[v]) dfs(v);
    }
}
dfs(s); // 从起点开始

图上的广度优先遍历(使用队列)

void bfs() {
    queue<T> q;
    q.push(s); // 起点入队
    vis[s] = 1;
    while (!q.empty()) {
        T u = q.front();
        q.pop();
        for (与 u 相邻的节点 v) {
            if (!vis[v]) {
                q.push(v);
                vis[v] = 1; // 必须在此处标记,避免某节点多次入队
            }
        }
    }
}

暴力搜索例题

图上的深度、广度遍历例题

搜索拓展-记忆化搜索

  • 注意:一定要满足无后效性!!
  • 搜索时进行缓存,对于缓存中已经存在的数据直接返回
  • 例题:P1434 滑雪

搜索拓展-剪枝

  • 最优性剪枝:如果当前方案已经比目前最优解更差,直接停止
  • 可行性剪枝:如果当前方案不可用,直接停止
  • 例题:B3624 猫粮规划

贪心

二分

二分查找

  • 在一个 有序!! 序列中查找某一元素的算法(\(O(logn)\)),每次将搜索范围缩小一半
  • 例题:P1947 猜数(交互题)

二分答案模板

题目答案特点:具有有界性、单调性!!

直接求解难,但是判定某解是否符合题意相对容易!

int binary_search(int L, int R, int k) {
    int l = L, r = R, ans = -1;
    while (l <= r) { // 必须写 l <= r!!!
        int mid = (l + r) >> 1;
        if (check(mid)) { // 自己写 check 函数
            把 mid 以某种方式赋值给 ans
            l = mid + 1; 或 r = mid - 1;
        } else {
            l = mid + 1; 或 r = mid - 1;
        }
    }
    return ans;
}

前缀和

  • 一种重要的预处理方式,大大降低查询的时间复杂度
  • 一维前缀和:\(f_i = f_{i-1} + a_i\)
  • 二维前缀和:\(f[i][j] = f[i-1][j] + f[i][j-1] - f[i-1][j-1] + a[i][j]\)
  • 多维前缀和:使用容斥原理解决
  • 查询 \([i,j]\) 的区间和:\(f_j - f_{i-1}\)

差分

  • 前缀和的逆运算
  • 主要作用:多次修改、一次查询(单点修改,区间查询)
  • 使用差分:f[l] += xf[r += 1] - x:把区间 \([l,r]\) 内所有的数字都加上 \(x\)

双指针

  • 同时使用两个指针
  • 利用区间有序性
  • 维护区间信息
  • 快慢指针:在单链表中找环
    • 跑步套圈,一个指针一次走一步,一个指针一次走两步
    • 两个指针相遇后,将其中一个移到表头,让两者都一步一步走,再度相遇的位置就是环的起点
  • 例题:P1102 A-B数对
  • 例题:P1147 连续自然数和

完结撒花!

posted @ 2023-07-27 10:01  SuperUser777  阅读(72)  评论(0)    收藏  举报