<#if settings.post_mathjax!false>

数据结构与算法模收集

admin
17
2025-06-15

Datastrucure and algorithm collection --

二分

multiset

有序集合,默认从小到大

multiset<int> ms;

可以自定义比较器使它从大到小排序

multiset<int,  greater<>> ms;

lower_bound

查找容器中第一个>= value的元素

#include <algorithm>
iterator lower_bound(iterator first, iterator last, const T& value);
vector<int> v = {1, 3, 5, 7, 9};  

int target = 110;  
auto it = lower_bound(v.begin(), v.end(), target);  

if (it != v.end()) {  
	cout << "第一个不小于 " << target << " 的值是 " << *it << endl;        } else {  
	cout << "所有元素都小于 " << target << endl;
}

upper_bound

返回第一个 > x 的元素位置

upper_bound(v.begin(), v.end(), x)

往前移动一个位置就是最后一个 <= k的元素

upper_bound(v.begin(), v.end(), x) - 1

二分答案

// **左满足(左true)**
int binary_search_l(int l, int r){
    while (l < r) {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
// **右满足(右true)**
int binary_search_r(int l, int r) {
    while (l < r) {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}
flowchart TD

    A[Christmas] -->|Get money| B(Go shopping)

    B --> C{Let me think}

    C -->|One| D[Laptop]

    C -->|Two| E[iPhone]

    C -->|Three| F[fa:fa-car Car]

滑动窗口

求最长窗口

枚举右端口,滑动左端口,窗口长度不减

/**  
 * 1208. 尽可能使字符串相等  
 * 将s[i]变为t[i]花费为|s[i] - t[i]|,求不超过花费maxCost,使s[l..r] == t[l..r]的最长 .[l..r]     
 * @param s     
 * @param t     
 * @param maxCost     
 * @return     
 * */    
	int equalSubstring(string s, string t, int maxCost) {  
	int n = (int) s.length();  
	int l = 0, r = 0, cost = 0/*, ans = 0*/;  
	for (r = 0; r < n; ++r) {  
		cost += abs(s[r] - t[r]);  
		// 考虑花费是否够  
		if (cost > maxCost) {  
			cost -= abs(s[l] - t[l]);  
			++l;  
		}  
//            ans = max(ans, r - l + 1);  
	}  
//        return ans;  
	// 由于这个滑动窗口每次最多滑入/划出一位,因此l-r+1本身就是最长的子串的长度。遍历完后n=r+1  
	return n - l;  
}
/**  
 * 904. 水果成篮  
 * @param fruits * @return */int totalFruit_k(vector<int>& fruits, int k) {  
    /*  
     * 找到最长的子串,元素种类<=k  
     */    int l = 0;  
    int n = (int) fruits.size();  
    unordered_map<int, int> cnt;  
    for (int r = 0; r < n; ++r) {  
        ++cnt[fruits[r]];  
        if (cnt.size() > k) {  
            --cnt[fruits[l]];  
            if (cnt[fruits[l]] == 0) cnt.erase(fruits[l]);  
            ++l;  
        }  
    }  
    return n - l;  
}  
int totalFruit(vector<int>& fruits) {  
    return totalFruit_k(fruits, 2);  
}

求最短窗口

枚举右端口,滑动左端口至不满足条件,比较答案。

/**  
 * 209. 长度最小的子数组  
 * 求nums中 和>=target的最短子数组  
 */
 int minSubArrayLen(int target, vector<int>& nums) {  
    int n = (int) nums.size();  
    if (target == 0 or std::reduce(nums.begin(), nums.end()) < target) {  
        return 0;  
    }  
    int l = 0, r = 0, minLen = INT_MAX;  
    ll s = 0;  
    // 枚举右窗口  
    while (r < n) {  
        s += nums[r++];  
        // 尝试弹出左端口  
        while (l <= r and s >= target) {    // 【注意】这时候比较运算符要取到=,因为r已经是下一个窗口右端口了  
            myPrintln(l, r)  
            s -= nums[l++];  // 【注意】这时候是像弹出左端口,再比较答案。
            minLen = min(r - l + 1, minLen);    // 在满足条件的情况下比较答案  
        }  
    }  
    return minLen;  
}
int minSubArrayLen(int target, vector<int>& nums) {
	int n = (int) nums.size();
	if (target == 0 or std::reduce(nums.begin(), nums.end()) < target) {
		return 0;
	}
	int l = 0, r = 0, minLen = INT_MAX;
	ll s = 0;
	// 枚举右窗口
	while (r < n) {
		s += nums[r++];
		// 尝试弹出左端口
		while (l <= r and s >= target) {    // 注意这时候比较运算符要取到=,因为r已经是下一个窗口右端口了
			minLen = min(r - l, minLen);    // 在满足条件的情况下比较答案
			s -= nums[l++];
		}
		// r++;
	}
	return minLen;
}

也可以用for循环的风格

int minSubArrayLen(int target, vector<int>& nums) {  
    int n = (int) nums.size();  
    if (target == 0 or std::reduce(nums.begin(), nums.end()) < target) {  
        return 0;  
    }  
  
    int l = 0, r = 0, minLen = INT_MAX;  
    ll s = 0;  
    // 枚举右窗口  
    while (r < n) {  
        s += nums[r];  
        // 尝试弹出左端口  
        while (l <= r and s >= target) {    // 注意这时候比较运算符要取到=,因为r已经是下一个窗口右端口了  
            minLen = min(r - l + 1, minLen);    // 在满足条件的情况下比较答案  
            s -= nums[l++];  
        }  
        r++;  
    }  
    return minLen;  
}
/**  
 * 2904. 最短且字典序最小的美丽子字符串  
 * 求s中 '1'的个数==k的最短子数组中 字典序最小的那个子数组  
 * @param s    
 * @param k    
 * @return    
 **/
 string shortestBeautifulSubstring(const string &s, int k) {  
	if (ranges::count(s, '1') < k) return "";  
	int n = (int) s.size(), n1 = 0;  
	string ans = s;  
	int l = 0, r = 0;  
	while (r < n) {  
		n1 += s[r++] == '1';  
		while (l <= r and n1 >= k) {  
			n1 -= s[l++] == '1';  
//                dbg( s.substr(l-1, r - l + 1))  
			string substr = s.substr(l-1, r - l + 1);  
			if (ans.size() > r - l + 1) {  
				ans = substr;  
			} elif (ans.size() == r - l + 1 and ans > substr) {  
				ans = substr;  
			}  
		}  
	}  
	return ans;  
}

求值为k的窗口有多少个

可以用差值法来解决,
值为k的窗口有多少个 = 值>=k的窗口有多少个 - 值>=k+1的窗口有多少个

	/**  
	 * 求和>=k的非空子数组有多少个  
	 * @param nums 
	 * @param k
	 * @return 
	 * */
	int numSubarraysWithSum_moreThan(vector<int>& nums, int k) {  
    ll ans = 0, summ = 0;  
    int n = (int) nums.size();  
    int l = 0, r = -1;  
    summ += nums[++r];  
    while (l < n and r < n) {  
        if (summ >= k and l <= r) {  
            ans += n - r;  
            summ -= nums[l++];  
        } else {  
            summ += nums[++r];  
        }  
    }  
    return ans;  
}  
/**  
 * 930. 和相同的二元子数组  
 * 求和为goal的非空子数组有多少个  
 * @param nums * @param goal * @return */int numSubarraysWithSum(vector<int>& nums, int goal) {  
    return numSubarraysWithSum_moreThan(nums, goal) - numSubarraysWithSum_moreThan(nums, goal+1);  
}

双指针

/**  
 * 反转  
 * @param s 
 * */
void reverseString(vector<char>& s) {  
    int l = 0, r = (int) s.size() - 1;  
    while (l < r) {  
        swap(s[l++], s[r--]);  
    }  
}  
  
/**  
 * 验证回文串  
 * @param s 
 * @return 
 * */
bool isPalindrome(const string &s) {  
    int l = 0, r = (int) s.size() - 1;  
    while (l < r) {  
        if (s[l++] != s[r--]) return false;  
    }  
    return true;  
}

同向双指针

/**  
 * 611. 有效三角形的个数  
 * 给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。  
 * @param nums 
 * @return 
 * */
int triangleNumber(vector<int>& nums) {  
    ranges::sort(nums);  
    int ans = 0, n = (int) nums.size();  
    for (int k = 2; k < n; ++k) {  
        int c = nums[k];  
        int i = 0;   // a=nums[i]  
        int j = k - 1;  // b=nums[j]  
        while (i < j) {  
            if (nums[i] + nums[j] > c) {  
                // 说明组合{nums[i ~ j-1], nums[j], nums[k]} 都是合法的  
                ans += j - i;  
                --j;  
            } else {  
                ++i;  
            }  
        }  
    }  
    return ans;  
}
/**  
 * 1574. 删除最短的子数组使剩余数组有序  
 * @param arr 
 * @return 
 * */
	int findLengthOfShortestSubarray(vector<int>& arr) {  
    /*  
     * 枚举左端点(l),移动右端点(r)。  
     * 即考虑移除arr[l..r]  
     */  
    int n = (int) arr.size(), r = n - 1;  
    // 找到递增后缀。即计算初始时l=0时 r的位置  
    while (r > 0 and arr[r - 1] <= arr[r]) {  
        --r;  
    }  
    if (r == 0) return 0;  
  
    int ans = r;    // 可以删除掉前缀 (删除 0 到 r - 1)  
    for (int l = 0; l == 0 or arr[l-1] <= arr[l] ; ++l) {  
        while (r < n and arr[r] < arr[l]) ++r;  // 计算最左的可以满足条件的r  
        ans = min(ans, r - l - 1);  
    }  
    return ans;  
}

单调栈

/**  
 * 739. 每日温度  
 * @param temperatures 
 * @return 
 * */
vector<int> dailyTemperatures(vector<int>& temperatures) {  
    int n = (int) temperatures.size();  
    vector ans(n, 0);  
    stack<int> stk;  
    for (int i = 0; i < n; ++i) {  
        while (!stk.empty() and temperatures[i] > temperatures[stk.top()]) {  
            int j = stk.top();  
            stk.pop();  
            ans[j] = i - j;  
        }  
        stk.push(i);  
    }  
    return ans;  
}

位运算

求二进制数中1的个数

int n1 = __builtin_popcount(num);

popcount((unsigned) num);

实现查询数组:求二进制数中1的个数

for (int i = 1; i <= mm; ++i) {  
    cnt1[i] = cnt1[i >> 1] + (i & 1);  
}

求一个数的二进制位数

bit_width((unsigned) num);

对一个数字二进制取反,但是不取反符号位

return ~num & ((1 << bit_width((unsigned )num)) - 1);

bitset<num>

通过一个数字初始化为位数组。
取反

auto x = bitset<18>(7);  
x = ~x;  
dbg(x);// to_string的时候是从高位往低位的。

auto x = bitset<18>(7);  
int mask = (1 << 18) - 1;  
x ^= mask;  
dbg(x);

数据结构

并查集

class UnionFind {  
    /**  
     * 不带秩并查集  
     */
 public:  
    vector<int> parent;  
  
public:  
    UnionFind(size_t n) {  
        parent.resize(n + 1);  
        iota(parent.begin(), parent.end(), 0);  // need #include <numeric>  
    }  
  
    int find(int index) {  
        if (index == parent[index]) {  
            return index;  
        }  
        parent[index] = find(parent[index]);  
        return parent[index];  
    }  
  
    void merge(int index1, int index2) {  
        parent[find(index1)] = find(index2);  
    }  
  
    bool connected(int index1, int index2) {  
        return find(index1) == find(index2);  
    }  
};
class DisjointedSet {  
    /**  
     * 带秩并查集  
     */
 public:  
    vector<int> parent; // 存储父节点  
    vector<int> rank;   // 存储树的秩(用于按秩合并)  
public:  
    /**  
     * 初始化并查集。n为元素个数  
     */    
     DisjointedSet(size_t n) {  
        parent.resize(n + 1);  
        rank.resize(n + 1, 0);  
        for (int i = 0; i <= n; ++i) {  
            parent[i] = i;  
        }  
    }  
  
    /**  
     * 查找操作,(带路径压缩)  
     */    
     int find(int x) {  
        while (x != parent[x]) {  
            x = parent[x] = parent[parent[x]];  
        }  
        return x;  
    }  
  
    /**  
     * 合并操作,按秩合并.  
     * 按秩合并(Union by Rank)是一种优化并查集合并操作的方法,目的是让树尽可能地保持平衡,从而减少树的高度,提升查找操作的效率。  
     *  在并查集中,秩通常表示树的高度(或深度)的近似值。  
     *  并查集的每个节点都有一个秩值,初始时所有节点的秩为 0。  
     *  合并两个集合时,比较它们的秩。  
     *  将秩较小的树的根节点连接到秩较大的树的根节点上。  
     *  如果两棵树的秩相同,则任选一棵树作为新的根,并将其秩加 1。  
     */    
     void merge(int x, int y) {  
        int rootX = find(x), rootY = find(y);  
        if (rootX != rootY) {  
            if ((rank[rootX] < rank[rootY])) {  
                parent[rootX] = rootY;  
            } else if (rank[rootX] > rank[rootY]) {  
                parent[rootY] = rootX;  
            } else {  
                parent[rootY] = rootX;  
                ++rank[rootX];  
            }  
        }  
    }  
  
    /**  
     * 判断两个节点是否属于同一集合  
     */    
     bool connected(int x, int y) {  
        return find(x) == find(y);  
    }  
};
class UnionFind {  
    vector<int> fa; // 代表元  
    vector<int> sz; // 集合大小  
public:  
    int cc; // 连通块个数  
    UnionFind(int n) : fa(n), sz(n, 1), cc(n) {  
        // 一开始有 n 个集合 {0}, {1}, ..., {n-1}        // 集合 i 的代表元是自己,大小为 1        ranges::iota(fa, 0); // iota(fa.begin(), fa.end(), 0);  
    }  
    // 返回 x 所在集合的代表元  
    // 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元  
    int find(int x) {  
        // 如果 fa[x] == x,则表示 x 是代表元  
        if (fa[x] != x) {  
            fa[x] = find(fa[x]); // fa 改成代表元  
        }  
        return fa[x];  
    }  
    // 判断 x 和 y 是否在同一个集合  
    bool is_same(int x, int y) {  
        // 如果 x 的代表元和 y 的代表元相同,那么 x 和 y 就在同一个集合  
        // 这就是代表元的作用:用来快速判断两个元素是否在同一个集合  
        return find(x) == find(y);  
    }  
    // 把 from 所在集合合并到 to 所在集合中  
    // 返回是否合并成功  
    bool merge(int from, int to) {  
        int x = find(from), y = find(to);  
        if (x == y) { // from 和 to 在同一个集合,不做合并  
            return false;  
        }  
        fa[x] = y; // 合并集合。修改后就可以认为 from 和 to 在同一个集合了  
        sz[y] += sz[x]; // 更新集合大小(注意集合大小保存在代表元上)  
        // 无需更新 sz[x],因为我们不用 sz[x] 而是用 sz[find(x)] 获取集合大小,但 find(x) == y,我们不会再访问 sz[x]        cc--; // 成功合并,连通块个数减一  
        return true;  
    }  
    // 返回 x 所在集合的大小  
    int get_size(int x) {  
        return sz[find(x)]; // 集合大小保存在代表元上  
    }  
};
class UnionFind:  
    def __init__(self, n: int):  
        # 一开始有 n 个集合 {0}, {1}, ..., {n-1}        # 集合 i 的代表元是自己,大小为 1        self._fa = list(range(n))  # 代表元  
        self._size = [1] * n  # 集合大小  
        self.cc = n  # 连通块个数  
    # 返回 x 所在集合的代表元  
    # 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元  
    def find(self, x: int) -> int:  
        # 如果 fa[x] == x,则表示 x 是代表元  
        if self._fa[x] != x:  
            self._fa[x] = self.find(self._fa[x])  # fa 改成代表元  
        return self._fa[x]  
    # 判断 x 和 y 是否在同一个集合  
    def is_same(self, x: int, y: int) -> bool:  
        # 如果 x 的代表元和 y 的代表元相同,那么 x 和 y 就在同一个集合  
        # 这就是代表元的作用:用来快速判断两个元素是否在同一个集合  
        return self.find(x) == self.find(y)  
    # 把 from 所在集合合并到 to 所在集合中  
    # 返回是否合并成功  
    def merge(self, from_: int, to: int) -> bool:  
        x, y = self.find(from_), self.find(to)  
        if x == y:  # from 和 to 在同一个集合,不做合并  
            return False  
        self._fa[x] = y  # 合并集合。修改后就可以认为 from 和 to 在同一个集合了  
        self._size[y] += self._size[x]  # 更新集合大小(注意集合大小保存在代表元上)  
        # 无需更新 _size[x],因为我们不用 _size[x] 而是用 _size[find(x)] 获取集合大小,但 find(x) == y,我们不会再访问 _size[x]        self.cc -= 1  # 成功合并,连通块个数减一  
        return True  
    # 返回 x 所在集合的大小  
    def get_size(self, x: int) -> int:  
        return self._size[self.find(x)]  # 集合大小保存在代表元上

树状数组

功能:

  1. 初始化:支持 N 个元素的树状数组。
  2. 单点更新:在索引 idx 处增加一个值 val
  3. 前缀和查询:查询 [1, idx] 区间的前缀和。
  4. 区间和查询:查询 [l, r] 的区间和(利用 sum(r) - sum(l-1) 计算)。
#include <iostream>

class FenwickTree {
private:
    int *bit; // 树状数组
    int size; // 数组大小

    // 计算最低位的1
    int lowbit(int x) {
        return x & (-x);
    }

public:
    // 构造函数
    FenwickTree(int n) {
        size = n;
        bit = new int[n + 1]; // 1-based index
        for (int i = 0; i <= n; i++) {
            bit[i] = 0;
        }
    }

    // 析构函数
    ~FenwickTree() {
        delete[] bit;
    }

    // 单点更新:在 idx 位置增加 val
    void update(int idx, int val) {
        while (idx <= size) {
            bit[idx] += val;
            idx += lowbit(idx);
        }
    }

    // 前缀和查询:求 [1, idx] 的和
    int query(int idx) {
        int sum = 0;
        while (idx > 0) {
            sum += bit[idx];
            idx -= lowbit(idx);
        }
        return sum;
    }

    // 区间和查询:求 [l, r] 的和
    int rangeQuery(int l, int r) {
        return query(r) - query(l - 1);
    }
};

// 测试代码
int main() {
    int n = 10;
    FenwickTree ft(n);

    // 初始化一些值
    ft.update(1, 5);
    ft.update(2, 3);
    ft.update(3, 7);
    ft.update(4, 9);
    ft.update(5, 6);

    std::cout << "前缀和 query(5): " << ft.query(5) << std::endl; // 5+3+7+9+6 = 30
    std::cout << "区间和 rangeQuery(2, 4): " << ft.rangeQuery(2, 4) << std::endl; // 3+7+9 = 19

    return 0;
}

代码解析

  1. lowbit(x) 计算 x 的最低位 1,即 x & (-x)
  2. update(idx, val) 更新 idx 位置的值,并向上级节点传播更新。
  3. query(idx) 计算 [1, idx] 的前缀和,通过回溯累加所有相关 bit 数组的值。
  4. rangeQuery(l, r) 计算 [l, r] 的区间和,利用 query(r) - query(l-1) 快速计算。
    时间复杂度
  • 单点更新: O(log N)
  • 前缀和查询: O(log N)
  • 区间查询: O(log N)
class FenwickTree {  
private:  
    std::vector<int> bit;  
    std::vector<int> arr;  
    int n;  
  
public:  
    FenwickTree(int size) : n(size), bit(size + 1, 0), arr(size + 1, 0) {}  
  
    FenwickTree(const std::vector<int>& inputArr) : n(inputArr.size()), bit(inputArr.size() + 1, 0), arr(inputArr.size() + 1, 0) {  
        for (int i = 1; i <= n; i++) {  
            set(i, inputArr[i - 1]);  
        }  
    }  
  
    FenwickTree(){}  
  
    // 单点更新,在 idx 位置增加 delta    void update(int idx, int delta) {  
        while (idx <= n) {  
            bit[idx] += delta;  
            idx += idx & -idx; // 低位操作  
        }  
    }  
  
    // 查询前缀和 [1, idx]    int query(int idx) {  
        int sum = 0;  
        while (idx > 0) {  
            sum += bit[idx];  
            idx -= idx & -idx;  
        }  
        return sum;  
    }  
  
    // 查询区间 [l, r] 的和  
    int rangeQuery(int l, int r) {  
        return query(r) - query(l - 1);  
    }  
  
    // 将第 i 个元素改为 x    void set(int idx, int x) {  
        int delta = x - arr[idx];  
        arr[idx] = x;  
        update(idx, delta);  
    }  
  
    // 获取第 i 个元素的值  
    int get(int idx) {  
        return arr[idx];  
    }  
};

线段树 -- 单点更新,区间查询 --V1

#include <iostream>
#include <vector>
#include <climits> // 用于INT_MAX

using namespace std;

class SegmentTree {
private:
    vector<int> arr; // 原始数组
    vector<int> segTree; // 线段树数组
    int n; // 数组大小

    void build(int index, int left, int right) {
        if (left == right) {
            segTree[index] = arr[left];
            return;
        }
        int mid = (left + right) / 2;
        build(2 * index, left, mid);
        build(2 * index + 1, mid + 1, right);
        segTree[index] = min(segTree[2 * index], segTree[2 * index + 1]);
    }

    void update(int index, int left, int right, int pos, int value) {
        if (left == right) {
            segTree[index] = value;
            return;
        }
        int mid = (left + right) / 2;
        if (pos <= mid) {
            update(2 * index, left, mid, pos, value);
        } else {
            update(2 * index + 1, mid + 1, right, pos, value);
        }
        segTree[index] = min(segTree[2 * index], segTree[2 * index + 1]);
    }

    int query(int index, int left, int right, int ql, int qr) {
        if (ql > right || qr < left) return INT_MAX; // 超出查询范围
        if (ql <= left && qr >= right) return segTree[index]; // 完全包含
        int mid = (left + right) / 2;
        return min(query(2 * index, left, mid, ql, qr), query(2 * index + 1, mid + 1, right, ql, qr));
    }

public:
    SegmentTree(const vector<int>& inputArr) {
        n = inputArr.size();
        arr = inputArr;
        segTree.resize(4 * n, INT_MAX);
        build(1, 0, n - 1);
    }

    void update(int pos, int value) {
        update(1, 0, n - 1, pos, value);
    }

    int query(int l, int r) {
        return query(1, 0, n - 1, l, r);
    }
};

int main() {
    int n, q;
    cin >> n >> q; // 读取数组大小和查询次数
    vector<int> inputArr(n);
    for (int i = 0; i < n; i++) cin >> inputArr[i];
    
    SegmentTree segTree(inputArr);
    
    while (q--) {
        char op;
        cin >> op;
        if (op == 'U') { // 更新操作 'U pos value'
            int pos, value;
            cin >> pos >> value;
            segTree.update(pos, value);
        } else if (op == 'Q') { // 查询操作 'Q l r'
            int l, r;
            cin >> l >> r;
            cout << segTree.query(l, r) << "\n";
        }
    }
    return 0;
}

线段树 -- 单点更新,区间查询 --V2

/**  
 * 线段树(单点更新,区间查询)  
 * 查询,更新的 数组下标从0开始  
 * 查询的区间是双端包含的([l..r])  
 */
template<typename T>  
class SegmentTree {  
private:  
    vector<T> arr; // 原始数组  
    vector<T> segTree; // 线段树数组  
    int n; // 数组大小  
    function<T(T,T)> opr; // 运算方式,比如max,min,+,*, ^ 等  
    T initialNum;  
  
    /**  
     * 递归构建线段树。  
     * @param index 根结点在segTree中的索引。整棵线段树的根结点一般是1.  
     * @param left 当前结点代表的区间的左边界(起始位置)。  
     * @param right 当前结点代表的区间的右边界(结束位置)。  
     */    void build(int index, int left, int right) {  
        if (left == right) {  
            segTree[index] = arr[left];  
            return;  
        }  
        int mid = (left + right) >> 1;  
        build(2 * index, left, mid);    // 构建左子树  
        build(2 * index + 1, mid + 1, right);   // 构建右子树  
        segTree[index] = opr(segTree[2 * index], segTree[2 * index + 1]);   // (递归)计算当前结点的数据  
    }  
  
    /**  
     * 单点更新  
     * @param index 根结点  
     * @param left (线段树)区间左端点  
     * @param right (线段树)区间右端点  
     * @param pos 更新位置  
     * @param value 新值  
     */    void update(int index, int left, int right, int pos, int value) {  
        if (left == right) {  
            segTree[index] = value;  
            return;  
        }  
        int mid = (left + right) >> 1;  
        if (pos <= mid) {  
            // 要跟新的点在左区间  
            update(2 * index, left, mid, pos, value);  
        } else {  
            // 要跟新的点在右区间  
            update(2 * index + 1, mid + 1, right, pos, value);  
        }  
        // 计算根结点的值  
        segTree[index] = opr(segTree[2 * index], segTree[2 * index + 1]);  
    }  
  
    /**  
     * 区间查询  
     * @param index 根结点  
     * @param left (线段树)区间左端点  
     * @param right (线段树)区间右端点  
     * @param ql 查询区间左端点  
     * @param qr 查询区间右端点  
     * @return 查询区间[ql..qr]  
     */    int query(int index, int left, int right, int ql, int qr) {  
        if (ql > right || qr < left) return initialNum; // 超出查询范围  
        if (ql <= left && qr >= right) return segTree[index]; // 完全包含  
        int mid = (left + right) >> 1;  
        return opr(query(2 * index, left, mid, ql, qr), query(2 * index + 1, mid + 1, right, ql, qr));  
    }  
  
public:  
    // 构建线段树。初始数组,运算方法。  
    SegmentTree(const vector<T>& inputArr, const string &operateMethod) {  
        if (operateMethod == "min") {  
            this->opr = (function<T(T,T)>) [&](T a, T b) {return min(a, b);};  
            this->initialNum = INT_MAX;  
        } else if (operateMethod == "max") {  
            this->opr = (function<T(T,T)>) [&](T a, T b){return max(a,b);};  
            this->initialNum = INT_MIN;  
        } else if (operateMethod == "+") {  
            this->opr = (function<T(T,T)>) [&](T a, T b) {return a + b;};  
            this->initialNum = 0;  
        } else if (operateMethod == "*") {  
            this->opr = (function<T(T,T)>) [&](T a, T b) {return a * b;};  
            this->initialNum = 1;  
        } else {  
            throw error_code();  
        }  
        n = inputArr.size();  
        arr = inputArr;  
        segTree.resize(4 * n, initialNum);  
        build(1, 0, n - 1);  
    }  
  
    /**  
     * 单点更新  
     * @param pos 位置下标(从0开始)  
     * @param value 更新为新值  
     */    void update(int pos, int value) {  
        this->update(1, 0, n - 1, pos, value);  
    }  
  
    /**  
     * 区间查询  
     * @param l 区间左端点(包含)  
     * @param r 区间右端点(包含)  
     * @return 查询区间[l..r]  
     */    int query(int l, int r) {  
        return this->query(1, 0, n - 1, l, r);  
    }  
  
    /**  
     * 二分查询第一个满足条件:query(begin, r) > val 的 r     * @param val     * @return     */    int firstSatisfied(int begin, T val) {  
        int l = 0, r = arr.size();  
        while (l < r) {  
            int mid = (l + r) >> 1;  
            if (query(begin, mid) > val) r = mid;  
            else l = mid + 1;  
        }  
        return  l <  arr.size() ? l : -1;  
    }  
  
  
    /**  
     * 二分查询最后一个满足条件:query(l, ..) > val 的 l     * @param val     * @return     */    int lastSatisfied(int end, T val) {  
        int l = 0, r = arr.size() - 1;  
        while (l < r) {  
            int mid = (l + r + 1) >> 1;  
            if (query(mid, r) > val) l = mid;  
            else r = mid - 1;  
        }  
        return l <= arr.size() ? l : -1;  
    }  
};

二分线段树 -- 单点更新,区间查询

/**
 * 线段树(单点更新,区间查询)
 * 查询、更新数组下标从0开始,区间查询为闭区间 [l, r]
 */
template<typename T>
class SegmentTree {
private:
    vector<T> arr;          // 原始数组
    vector<T> segTree;      // 线段树数组,1-indexed
    int n;                  // 数组大小
    function<T(T,T)> opr;   // 运算方式,例如 min, max, +, *
    T initialNum;           // 初始值

    void build(int index, int left, int right) {
        if (left == right) {
            segTree[index] = arr[left];
            return;
        }
        int mid = (left + right) >> 1;
        build(2 * index, left, mid);
        build(2 * index + 1, mid + 1, right);
        segTree[index] = opr(segTree[2 * index], segTree[2 * index + 1]);
    }

    void update(int index, int left, int right, int pos, int value) {
        if (left == right) {
            segTree[index] = value;
            return;
        }
        int mid = (left + right) >> 1;
        if (pos <= mid)
            update(2 * index, left, mid, pos, value);
        else
            update(2 * index + 1, mid + 1, right, pos, value);
        segTree[index] = opr(segTree[2 * index], segTree[2 * index + 1]);
    }

    int query(int index, int left, int right, int ql, int qr) {
        if (qr < left || ql > right)
            return initialNum;
        if (ql <= left && right <= qr)
            return segTree[index];
        int mid = (left + right) >> 1;
        return opr(query(2 * index, left, mid, ql, qr),
                   query(2 * index + 1, mid + 1, right, ql, qr));
    }

    // 内部函数:利用线段树结构,在区间 [ql, qr] 内,给定当前累积值 current,
    // 查找第一个位置 i,使得 opr(current, seg[ql...i]) > val。
    // current 作为引用传入,搜索过程中更新累积值。
    int query_first(int idx, int L, int R, int ql, int qr, T &current, T val) {
        // 当前区间与查询区间没有交集
        if (R < ql || L > qr) return -1;
        // 当前区间完全包含在查询区间内
        if (ql <= L && R <= qr) {
            T combined = opr(current, segTree[idx]);
            // 如果整块区间累积后仍不满足条件,直接更新 current 并返回 -1
            if (combined <= val) {
                current = combined;
                return -1;
            }
            // 否则,这一块内必存在满足条件的位置
            // 当区间退化为叶子时,直接返回该位置
            while (L < R) {
                int mid = (L + R) >> 1;
                int leftIdx = 2 * idx, rightIdx = 2 * idx + 1;
                T leftCombined = opr(current, segTree[leftIdx]);
                if (leftCombined > val) {
                    // 满足条件,则在左子树中继续查找
                    idx = leftIdx;
                    R = mid;
                } else {
                    // 左子树不满足,累积左子树的结果后转向右子树
                    current = leftCombined;
                    idx = rightIdx;
                    L = mid + 1;
                }
            }
            return L;
        }
        int mid = (L + R) >> 1;
        int leftAns = query_first(2 * idx, L, mid, ql, qr, current, val);
        if (leftAns != -1)
            return leftAns;
        return query_first(2 * idx + 1, mid + 1, R, ql, qr, current, val);
    }

    // 内部函数:利用线段树结构,在区间 [ql, qr] 内,给定当前累积值 current,
    // 查找最后一个位置 i,使得 opr( seg[i...qr], current ) > val。
    // 这里我们“反向”累积,即从右侧开始查找。为了简化实现,我们将查询区间反转思路。
    int query_last(int idx, int L, int R, int ql, int qr, T &current, T val) {
        if (R < ql || L > qr) return -1;
        if (ql <= L && R <= qr) {
            T combined = opr(segTree[idx], current);
            if (combined <= val) {
                current = combined;
                return -1;
            }
            while (L < R) {
                int mid = (L + R) >> 1;
                int leftIdx = 2 * idx, rightIdx = 2 * idx + 1;
                // 优先检查右子树,因为我们要找到最大的下标
                T rightCombined = opr(segTree[rightIdx], current);
                if (rightCombined > val) {
                    idx = rightIdx;
                    L = mid + 1;
                } else {
                    current = opr(segTree[rightIdx], current);
                    idx = leftIdx;
                    R = mid;
                }
            }
            return L;
        }
        int mid = (L + R) >> 1;
        int rightAns = query_last(2 * idx + 1, mid + 1, R, ql, qr, current, val);
        if (rightAns != -1)
            return rightAns;
        return query_last(2 * idx, L, mid, ql, qr, current, val);
    }

public:
    // 构造函数:根据初始数组和运算方式构造线段树
    SegmentTree(const vector<T>& inputArr, const string &operateMethod) {
        if (operateMethod == "min") {
            this->opr = [&](T a, T b) { return min(a, b); };
            this->initialNum = numeric_limits<T>::max();
        } else if (operateMethod == "max") {
            this->opr = [&](T a, T b) { return max(a, b); };
            this->initialNum = numeric_limits<T>::min();
        } else if (operateMethod == "+") {
            this->opr = [&](T a, T b) { return a + b; };
            this->initialNum = 0;
        } else if (operateMethod == "*") {
            this->opr = [&](T a, T b) { return a * b; };
            this->initialNum = 1;
        } else {
            throw runtime_error("Unsupported operation");
        }
        n = inputArr.size();
        arr = inputArr;
        segTree.resize(4 * n, initialNum);
        build(1, 0, n - 1);
    }

    // 单点更新:更新下标 pos 为新值 value
    void update(int pos, int value) {
        update(1, 0, n - 1, pos, value);
    }

    // 区间查询:[l, r](两端闭区间)
    int query(int l, int r) {
        return query(1, 0, n - 1, l, r);
    }

    /**
     * 利用线段树内部结构查找第一个满足条件的位置:
     * 找到最小的 r,使得 query(begin, r) > val
     * 若不存在,则返回 -1
     */
    int firstSatisfied(int begin, T val) {
        if (begin < 0 || begin >= n) return -1;
        // 若整个区间 [begin, n-1] 都不满足,则直接返回 -1
        if (query(begin, n - 1) <= val)
            return -1;
        T current = initialNum;
        return query_first(1, 0, n - 1, begin, n - 1, current, val);
    }

    /**
     * 利用线段树内部结构查找最后一个满足条件的位置:
     * 找到最大的 l,使得 query(l, end) > val
     * 若不存在,则返回 -1
     */
    int lastSatisfied(int end, T val) {
        if (end < 0 || end >= n) return -1;
        // 若整个区间 [0, end] 都不满足,则直接返回 -1
        if (query(0, end) <= val)
            return -1;
        T current = initialNum;
        return query_last(1, 0, n - 1, 0, end, current, val);
    }
};

离散结构

#include <iostream>
#include <vector>
#include <unordered_map>
#include <queue>

using namespace std;

// 二叉树节点定义
struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

// 二叉树类
class BinaryTree {
private:
    TreeNode* root;  // 根节点
    int maxDiameter; // 记录最大直径
    unordered_map<int, int> inorderIndex;  // 记录中序遍历索引,便于快速查找
    int postIndex;  // 后序遍历当前索引(从后向前遍历)

	// 递归构建二叉树
    TreeNode* buildTreeHelper(const vector<int>& preorder, const vector<int>& inorder, int inLeft, int inRight) {
        if (inLeft > inRight) return nullptr;

        int rootVal = preorder[preIndex++];  // 当前前序遍历的根节点
        TreeNode* node = new TreeNode(rootVal);
        int inIndex = inorderIndex[rootVal];  // 在中序遍历中的索引

        // 先构建左子树,再构建右子树
        node->left = buildTreeHelper(preorder, inorder, inLeft, inIndex - 1);
        node->right = buildTreeHelper(preorder, inorder, inIndex + 1, inRight);

        return node;
    }

    // 递归构建二叉树
    TreeNode* buildTreeHelper(const vector<int>& inorder, const vector<int>& postorder, int inLeft, int inRight) {
        if (inLeft > inRight) return nullptr;

        int rootVal = postorder[postIndex--];  // 当前后序遍历的根节点
        TreeNode* node = new TreeNode(rootVal);
        int inIndex = inorderIndex[rootVal];  // 在中序遍历中的索引

        // 先构建右子树,再构建左子树(因为 postIndex 从右往左遍历)
        node->right = buildTreeHelper(inorder, postorder, inIndex + 1, inRight);
        node->left = buildTreeHelper(inorder, postorder, inLeft, inIndex - 1);

        return node;
    }


	// 递归构建二叉树
    TreeNode* buildTreeHelper(const vector<int>& preorder, const vector<int>& postorder, int postLeft, int postRight) {
        if (postLeft > postRight) return nullptr;

        int rootVal = preorder[preIndex++];  // 当前前序遍历的根节点
        TreeNode* node = new TreeNode(rootVal);

        // 叶子节点,直接返回
        if (postLeft == postRight) return node;

        // 前序遍历的下一个节点是左子树的根
        int leftRootVal = preorder[preIndex];
        int leftRootIndex = postorderIndex[leftRootVal]; // 找到左子树根在后序遍历中的索引

        // 递归构建左子树和右子树
        node->left = buildTreeHelper(preorder, postorder, postLeft, leftRootIndex);
        node->right = buildTreeHelper(preorder, postorder, leftRootIndex + 1, postRight - 1);

        return node;
    }

	// 计算以当前节点为根的子树深度,并更新最大直径
    int depth(TreeNode* node) {
        if (!node) return 0;

        int leftDepth = depth(node->left);
        int rightDepth = depth(node->right);

        // 更新全局最大直径
        maxDiameter = max(maxDiameter, leftDepth + rightDepth);

        // 返回当前节点的深度
        return max(leftDepth, rightDepth) + 1;
    }


    // 获取左视图(BFS 层序遍历)
    vector<int> getLeftView(TreeNode* root) {
        vector<int> leftView;
        if (!root) return leftView;

        queue<TreeNode*> q;
        q.push(root);
        
        while (!q.empty()) {
            int levelSize = q.size();
            leftView.push_back(q.front()->val);  // 记录每层第一个节点

            for (int i = 0; i < levelSize; i++) {
                TreeNode* node = q.front();
                q.pop();
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }
        return leftView;
    }

    // 获取右视图(BFS 层序遍历)
    vector<int> getRightView(TreeNode* root) {
        vector<int> rightView;
        if (!root) return rightView;

        queue<TreeNode*> q;
        q.push(root);
        
        while (!q.empty()) {
            int levelSize = q.size();
            rightView.push_back(q.back()->val);  // 记录每层最后一个节点

            for (int i = 0; i < levelSize; i++) {
                TreeNode* node = q.front();
                q.pop();
                if (node->left) q.push(node->left);
                if (node->right) q.push(node->right);
            }
        }
        return rightView;
    }

public:
    // 构造函数。通过中序序列,后续序列构建二叉树
    BinaryTree(const vector<int>& inorder, const vector<int>& postorder) {
        postIndex = postorder.size() - 1;
        for (int i = 0; i < inorder.size(); i++) {
            inorderIndex[inorder[i]] = i;  // 预处理中序索引
        }
        root = buildTreeHelper(inorder, postorder, 0, inorder.size() - 1);
    }

	// 构造函数。通过前、中序
    BinaryTree(const vector<int>& preorder, const vector<int>& inorder) {
        preIndex = 0;
        for (int i = 0; i < inorder.size(); i++) {
            inorderIndex[inorder[i]] = i;  // 预处理中序索引
        }
        root = buildTreeHelper(preorder, inorder, 0, inorder.size() - 1);
    }

	// 构造函数。前序、后序
    BinaryTree(const vector<int>& preorder, const vector<int>& postorder) {
        preIndex = 0;
        for (int i = 0; i < postorder.size(); i++) {
            postorderIndex[postorder[i]] = i;  // 预处理后序索引
        }
        root = buildTreeHelper(preorder, postorder, 0, postorder.size() - 1);
    }
    
    // 计算并返回二叉树的直径
    int getDiameter() {
        depth(root);
        return maxDiameter;
    }
    
    // 打印左视图
    void printLeftView() {
        vector<int> leftView = getLeftView(root);
        cout << "L:";
        for (int val : leftView) {
            cout << " " << val;
        }
        cout << endl;
    }

    // 打印右视图
    void printRightView() {
        vector<int> rightView = getRightView(root);
        cout << "R:";
        for (int val : rightView) {
            cout << " " << val;
        }
        cout << endl;
    }

    // 析构函数,递归删除所有节点
    ~BinaryTree() {
        destroyTree(root);
    }

private:
    void destroyTree(TreeNode* node) {
        if (!node) return;
        destroyTree(node->left);
        destroyTree(node->right);
        delete node;
    }
};

DP

背包问题

01背包问题

/**  
     * 01-背包问题  
     * @param W 背包容量(背包最多能装多重的物品)  
     * @param weights  每个物品的重量  
     * @param values  每个物品的价值  
     * @return  背包所装物品的最大价值  
     */    int knapsack_01(int W, const vector<int>& weights, const vector<int>& values) {  
        int n = (int) weights.size();  
        // dp[i][w]表示前i个物品,总重量不超过w时的最大价值  
        // 实际上就是取一个物品的子序列,求子序列的最大权值和  
        vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));  
  
        // 动态规划过程  
        for (int i = 1; i <= n; ++i) {  
            for (int w = 0; w <= W; ++w) {  
                if (weights[i - 1] <= w) {  
                    // 物品i可以放  
                    //  i不放:dp[i][w] = dp[i-1][w]  
                    //  i放:dp[i][w] = dp[i-1][w - w[i]] + v[i]  
  
                    // 因为weights和values下标是从0开始的,而dp下标是从1开始的,因此第i个物品的下标是i-1  
                    dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i-1]);  
                } else {  
                    // 物品i放不下  
                    dp[i][w] = dp[i - 1][w];  
                }  
            }  
        }  
  
        return dp[n][W];  
    }  
    void testKnapsack_01() {  
        /*4 10  
        3 4        2 3        4 5        5 7*/        int n, W;  
//        cout << "请输入物品个数和背包容量: ";  
        cin >> n >> W;  
  
        vector<int> weights(n), values(n);  
//        cout << "请输入每个物品的重量和价值: " << endl;  
        for (int i = 0; i < n; ++i) {  
            cin >> weights[i] >> values[i];  
        }  
  
        int result = knapsack_01(W, weights, values);  
//        cout << "最大总价值是: " << result << endl;  
        cout <<  result << endl;  
    }
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int n, W;
    cout << "Enter number of items and capacity of the knapsack: ";
    cin >> n >> W;

    vector<int> weights(n), values(n);
    cout << "Enter the weights of the items: ";
    for (int i = 0; i < n; ++i) {
        cin >> weights[i];
    }

    cout << "Enter the values of the items: ";
    for (int i = 0; i < n; ++i) {
        cin >> values[i];
    }

    // 创建一个一维dp数组,用于存储背包容量为w时的最大价值
    vector<int> dp(W + 1, 0);

	/*
		这里采用方向遍历法,防止过分累加
	*/
    // 动态规划过程
    for (int i = 0; i < n; ++i) {
        // 从后往前遍历,确保每次使用的dp值是上一轮的结果
        for (int w = W; w >= weights[i]; --w) {
            dp[w] = max(dp[w], dp[w - weights[i]] + values[i]);
        }
    }

    // 输出结果
    cout << "Maximum value in knapsack of capacity " << W << " is: " << dp[W] << endl;

    return 0;
}

最长公共子序列(LCS)

/**  
 * 1143. 最长公共子序列  
 * @param text1     * @param text2     * @return text1 和 text2 的最长公共子序列的长度  
 */    
 int longestCommonSubsequence(const string &text1, const string &text2) { 
	/*  
	 * 这个过程实际上类似于网格DP  
	 */        size_t n1 = text1.length(), n2 = text2.length();  

	// 假设text1,text2下标都是从1开始的  
	vector dp(n1 + 1, vector(n2 + 1, (size_t) 0));   // dp[i][j]表示text1[0..i]和text2[0..j]中最长公共子序列  
	/*  
	 * 遍历两个字符串  
	 */        
	 for (int i = 1; i <= n1; ++i) {  
		for (int j = 1; j <= n2; ++j) {  
			if (text1[i - 1] == text2[j - 1]) {  
				// 如果这两个字符相等  
//                    dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);  
				dp[i][j] = dp[i - 1][j - 1] + 1;  
			} else {  
				// 如果这两个字符不相等,取大的情况  
				// 这个过程有点像区间DP中的区间合并  
				dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);  
			}  
		}  
	}  
	return (int) dp[n1][n2];  
}

最长递增子序列(LIS)

/**  
 * 300. 最长递增子序列  
 * @param nums     * @return     */    int lengthOfLIS(vector<int>& nums) {  
	// 元素迭代法  
	map<int, int> f;    // f[i] 表示以i结尾的最长公共子序列的长度  
	for (int num: nums) {  
		if (!f.contains(num)) { // 以i为结尾的子序列长度至少为1  
			f[num] = 1;  
		}  
		// 遍历之前的子序列,看能否接在末尾  
		for (auto &[k,v]: f) {  
			if (num > k) {  
				f[num] = max(f[num], v + 1);  
			}  
		}  
	}  
	int ans = 1;  
	for (auto &[k, v]: f) {  
		ans = max(ans, v);  
	}  
	return ans;  
}  
void test_lengthOfLIS() {  
	vecI v = {10,9,2,5,3,7,101,18};  
//        vecI v = {10,9,2,5,3};  
//        vecI v = {3,2,1, 2,3};  
	auto  
	res = lengthOfLIS(v);  
	dbg(res)  
}  
int lengthOfLIS1(vector<int>& nums) {  
	// 腾讯面试题(2025年03月04日)  
	// 做法2:二分,是一个思路题。思考 7891234 这个例子,维护一个 数组,把 nums[i] 插入到 arr[i - 1] < nums[i] 的位置(严格递增),替代 arr[i]        /*         * 元素迭代法 + 贪心优先法:  
	 *  我们应该尽量使子序列的末尾最小,这样才能有更大的扩展空间。  
	 */        vector<int> arr;  
	for (int num : nums) {  
		// 如果要求严格递增的最长子序列长度,用lower_bound。如果非严格递增,用upper_bound。如果求递减,将元素每个数字变为相反数即可。
		int id = (int) (std::lower_bound(arr.begin(), arr.end(), num) - arr.begin());  
		// id 是 nums[i] 应该被插入的位置。  
		if (id < arr.size()) {  
			arr[id] = num;  
		}  
		else {  
			arr.push_back(num);  
		}  
	}  
	return (int)arr.size();  
}

划分型DP

/**  
 * 139. 单词拆分  
 * 求是否可以将s拆分成wordDict元素组成的集  
 * @param s 
 * @param wordDict 
 * @return 
 * */
bool wordBreak(string s, vector<string>& wordDict) {  
    set<string> ws(wordDict.begin(), wordDict.end());  
    vecI wordLengths;   // 每个word的长度  
    for (auto &ss: ws) {  
        wordLengths.push_back((int)ss.length());  
    }  
  
    int n = (int) s.length();  
    vector canPartition(n+1, false);  
    canPartition[0] = true;  
  
    // 枚举右端点,判断它的右侧是否可以划分  
    for (int i = 0; i < n; ++i) {  
        int l;  
        for (int len: wordLengths) {  
            l = i + 1 - len;  
            if (l >= 0 and canPartition[l] and ws.contains(s.substr(l, len))){  
                canPartition[i+1] = true;  
                break;  
            }  
        }  
    }  
    return canPartition[n];  
}  
void test_wordBreak() {  
    vector<string>  
    v1 = {"cats","dog","sand","and","cat"};  
    auto  
    res = this->wordBreak("catsandog", v1);  
    dbg(res)  
}

状态机DP

/**  
 * 121. 买卖股票的最佳时机 交易一次  
 * 找到 $i<j$ 使 $prices[j] - prices[i]$ 最大  
 * @param prices * @return */int maxProfit_1(vector<int>& prices) {  
    int ans = 0;  
    int minPrice = prices[0];   // prices的前缀最小  
    for (int p: prices) {  
        ans = max(ans, p - minPrice);  
        minPrice = min(minPrice, p);  
    }  
    return ans;  
}  
  
/**  
 * 122. 买卖股票的最佳时机 II * 交易次数不限  
 * 相当于求一个离散数列的所有上升差分值和  
 * @param prices * @return */int maxProfit_2_1(vector<int>& prices) {  
    int ans = 0;  
    for (int i = 1; i < prices.size(); ++i) {  
        int d = prices[i] - prices[i-1];  
        ans += d > 0 ? d :0;  
    }  
    return ans;  
}  
  
/**  
 * 122. 买卖股票的最佳时机 II * 交易次数不限  
 * 状态机DP解法  
 * @param prices * @return */int maxProfit_2_2(vector<int>& prices) {  
    // prices[i]表示第i天的股价,i从0开始  
    // 这里的股票,把它看成是一件物品更好理解。如果你以5块钱买下这件物品,明天以8块钱卖出,那么你就赚了3块钱。  
    int f0 = 0, f1 = INT_MIN;   // f0表示【不持有】这件物品,此时你的利润。f1表示【持有】这件物品,此时你的利润  
    // 为了考虑边界情况,我们让第-1天的股价为inf  
    // 这里也是采用元素迭代法,迭代每日的价格,更新f0,f1。  
    for (int p: prices) {  
        int pre_f0 = f0, pre_f1 = f1;  
        /*  
         * 考虑今天是否要继续持有这件物品。  
         *  - 不持有:  
         *    - 昨天也不持有:f0 = pre_f0  
         *    - 昨天持有(今天卖出):f0 = pre_f1 + p  
         *      两者取max  
         *  - 持有:  
         *    - 昨天不持有(买入):f1 = pre_f0 - p  
         *    - 昨天持有:f1 = pre_f1  
         *      两者还是取max  
         */        f0 = max(pre_f0, pre_f1 + p),  
                f1 = max(pre_f0 - p, pre_f1);  
    }  
    return f0;  
}  
  
/**  
 * 188. 买卖股票的最佳时机 IV * @param k 最多交易k笔  
 * @param prices    每日价格  
 * @return 最大利润  
 */int maxProfit(int k, vector<int>& prices) {  
    // 【注意】买入+卖出算一笔交易。  
    int n = prices.size();  
    if (n == 0 || k == 0) return 0;  
  
    // 特殊情况:如果 k >= n/2,相当于无限次交易  
    if (k >= n/2) return maxProfit(prices);  
  
    vector<array<int, 2> > f(k+2, {INT_MIN / 2, INT_MIN / 2});  // f[j][0/1] 表示第j笔交易,不持有(卖出)/持有(买入)可以获得的最大利润(积累的最大财富)  
    // 初始,将每笔交易,卖出状态的利润设为0,买入状态的理论设为-inf(和之前一样,假设第0天的市场价是inf,这时候最优的选择是第0天不买入)  
    for (int j = 1; j <= k+1; ++j) {  
        f[j][0] = 0;  
    }  
    // 还是用元素迭代法,迭代每日价格,更新利润  
    for (int p: prices) {  
        // 采用倒序遍历  
        // 这里是因为同一天之内进行一次买入卖出,因此采用倒序遍历法防止过分累加  
        for (int j = k + 1; j > 0; --j) {  
            // 第j笔交易,卖出的利润就是 $当天的价格 - 上一次买入的价格$ => $第j笔买入时积累的财富 + p$, 即 $f[j][1] + p$。  
            // 第j笔交易,买入,这时候积累的财富就是 $上笔交易卖出时积累的财富 - 当天的价格$ =>  $第j-1笔买出时积累的财富 - p$, 即 $f[j-1][0] - p$。  
            // 每次迭代更新都要自比较一次  
            f[j][0] = max(f[j][0], f[j][1] + p);    // 卖出  
            f[j][1] = max(f[j][1], f[j-1][0] - p); // 买入  
        }  
    }  
    return f[k+1][0];  
}

区间DP

方法一:梅丽DP枚举法:枚举一个端点,通过枚举另一个端点得到这个端点内所有的子区间,扩展区间从而实现全部枚举。

/**  
 * 516. 最长回文子序列 的长度  
 * @param s 
 * @return 
 * */
int longestPalindromeSubseq(string s) {  
    int n = (int) s.size();  
    vector f(n, vector(n, 0));  
    // 倒序枚举区间左端点  
    for (int i = n-1; i >= 0; --i) {  
        f[i][i] = 1;  
        // 枚举区间右端点  
        for (int j = i + 1; j < n; ++j) {  
            // 合并区间  
            f[i][j] = (s[i] == s[j] ? f[i+1][j-1] + 2 : max(f[i+1][j], f[i][j-1]));  
        }  
    }  
    // 为什么要倒序枚举?因为我们要求区间长度为len的答案时,必须求他它的所有区间长度为len-1的答案。  
    // 因此其实也可以用:枚举右端点j -> 枚举左端点[0..j-1]  
    return f[0][n-1];  
}


方法二:克罗多枚举法:枚举区间长度,扩展区间长度,从而实现全部枚举。

int longestPalindromeSubseq(const string &s) {  
    int n = s.length();  
    int dp[n][n];  
    memset(dp, 0, sizeof(dp));  
  
    // 单个字符本身就是回文  
    for (int i = 0; i < n; i++) {  
        dp[i][i] = 1;  
    }  
  
    // 逐步扩展子串长度  
    for (int len = 2; len <= n; len++) {  
        for (int i = 0; i + len - 1 < n; i++) {  
            int j = i + len - 1;  
            if (s[i] == s[j]) {  
                dp[i][j] = dp[i + 1][j - 1] + 2;  
            } else {  
                dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);  
            }  
        }  
    }  
  
    return dp[0][n - 1];  // 求整个字符串的最长回文子序列  
}

图论

拓扑排序

vector<int> topologicalSort(const vector<vector<bool>> &adjMatrix/*邻接矩阵*/) {  
    size_t n = adjMatrix.size();  
    vector<int> inDegree(n, 0);  
    vector<int> result;  
  
    // 计算每个节点的入度  
    for (int i = 0; i < n; ++i) {  
        for (int j = 0; j < n; ++j) {  
            if (adjMatrix[i][j] != 0) {  
                ++inDegree[j];  
            }  
        }  
    }  
  
    // 使用队列存储入度为0的节点  
    queue<int> zeroInDegree;  
    for (int i = 0; i < n; ++i) {  
        if (inDegree[i] == 0) zeroInDegree.push(i);  
    }  
  
    // 开始拓扑排序  
    while (!zeroInDegree.empty()) {  
        int node = zeroInDegree.front();  
        zeroInDegree.pop();  
        result.push_back(node);  
  
        // 遍历当前节点的所有邻居  
        for (int j= 0; j < n; ++j) {  
            // 断开和邻居的边  
            if (adjMatrix[node][j] != 0) {  
                --inDegree[j];  
                // 将入度为0的邻居添加到队列中。  
                if (inDegree[j] == 0) zeroInDegree.push(j);  
            }  
        }  
  
    }  
    // 如果排序结果的大小不等于节点数,说明图中存在环,无法拓扑排序  
    if (result.size() != n) {  
        dbg("ring!")  
        return {};  
    }  
    return result;  
}
/**  
 * 拓扑排序  
 * @param edges 边合集  
 * @param numNodes * @return   
 */  
std::vector<int> topologicalSort(const std::vector<std::vector<int>>& edges, int numNodes) {  
    // 构建邻接表和入度表  
    std::vector<std::vector<int>> adjList(numNodes);  
    std::vector<int> inDegree(numNodes, 0);  
  
    for (const auto& edge : edges) {  
        int u = edge[0];  
        int v = edge[1];  
        adjList[u].push_back(v);  
        inDegree[v]++;  
    }  
  
    // 使用队列存储入度为 0 的节点  
    std::queue<int> zeroInDegree;  
    for (int i = 0; i < numNodes; ++i) {  
        if (inDegree[i] == 0) {  
            zeroInDegree.push(i);  
        }  
    }  
  
    // 结果存储拓扑排序  
    std::vector<int> topoOrder;  
  
    while (!zeroInDegree.empty()) {  
        int node = zeroInDegree.front();  
        zeroInDegree.pop();  
        topoOrder.push_back(node);  
  
        // 遍历邻居节点,减少入度  
        for (int neighbor : adjList[node]) {  
            inDegree[neighbor]--;  
            if (inDegree[neighbor] == 0) {  
                zeroInDegree.push(neighbor);  
            }  
        }  
    }  
  
    // 如果结果中的节点数不等于图中的节点数,说明有环  
    if (topoOrder.size() != numNodes) {  
        throw std::runtime_error("The graph contains a cycle and cannot be topologically sorted.");  
    }  
    return topoOrder;  
}

Dijkstra算法函数

/**  
 * Dijkstra算法函数  
 * @param adjList   邻接表  
 * @param start     起点  
 */
 void dijkstra(const vector<vector<pII>>& adjList, int start) {  
    /*  
     * dijkstra算法本质上就是一个 最短距离(离起点最近的节点)优先搜索算法。  
     */    
    int vertices = (int)adjList.size();  // @param vertices 顶点数  
    vecI dist(vertices, INT_MAX);  
    dist[start] = 0;    // 起点到起点的距离是0  
  
    // 优先队列(最小堆),按距离从小到大排序。  
    priority_queue<pII, vector<pII>, greater<> > pq; // @element {a,b} 表示b点到起点的最短距离为a。  
    pq.emplace(0, start);    //添加起点到搜索队列  
  
    while (!pq.empty()) {  
        // 取出当前距离最小的点x,以及它到起点的距离d  
        int x = pq.top().second, d = pq.top().first;  
        pq.pop();  
  
        if (d > dist[x]) continue;  
  
        for (auto &edge: adjList[x]) {  // 遍历x的邻居  
            int y = edge.first, distXY = edge.second;  
            if (dist[x] + distXY < dist[y]) {   // 如果通过x点和这条边edge到达y点,比之前到达y点的方法距离更短,则更新  
                dist[y] = dist[x] + distXY;  
                pq.emplace(dist[y], y);  
            }  
        }  
    }  
    // 输出每个点的最短距离  
    for (int i = 0; i < vertices; i++) {  
        if (dist[i] == INT_MAX) {  
            cout << "从点 " << start << " 到点 " << i << " 无法到达" << endl;  
        } else {  
            cout << "从点 " << start << " 到点 " << i << " 的最短距离为: " << dist[i] << endl;  
        }  
    }  
}

int _test_dijkstra() {  
    int vertices, edges;  
    cout << "请输入图的顶点数和边数: ";  
    cin >> vertices >> edges;  
  
    // 邻接表表示图  
    vector<vector<pII> > adjList(vertices);  
  
    cout << "请输入每条边的信息 (起点, 终点, 权重):" << endl;  
    for (int i = 0; i < edges; i++) {  
        int u, v, w;  
        cin >> u >> v >> w;  
        adjList[u].push_back({v, w});  
        adjList[v].push_back({u, w});  // 无向图,注释掉这行用于有向图  
    }  
  
    int start;  
    cout << "请输入起始顶点: ";  
    cin >> start;  
  
    dijkstra(adjList, start);  
  
    return status.OK;  
}

Floyd-Warshall

/**  
 * Floyd-Warshall算法  
 * @param n 结点数(结点从0开始)  
 * @param dist 带权邻接矩阵,也是最终结果查询数组。这里是原地修改。  
 */void floydWarshall(const int n, vector<vector<int> > &dist) {  
    const int INF = INT_MAX >> 1;  
    for (int k = 0; k < n; ++k) {  
        for (int i = 0; i < n; ++i) {  
            for (int j = 0; j < n; ++j) {  
                // 如果通过k能使i到j的路径更短,则更新dist[i][j]  
                if (dist[i][k] != INF and dist[k][j] != INF and dist[i][j] > dist[i][k] + dist[k][j]) dist[i][j] = dist[i][k] + dist[k][j];  
            }  
        }  
    }  
  
    cout << "最短路径矩阵:" << endl;  
    for (int i = 0; i < n; ++i) {  
        for (int j = 0; j < n; ++j) {  
            if (dist[i][j] == INF) {  
                cout << "INF ";  
            } else {  
                cout << dist[i][j] << " ";  
            }  
        }  
        cout << endl;  
    }  
}

最小生成树之Kruskal 算法(一般用于稀疏图)

Kruskal 算法是一种用于求解最小生成树(MST, Minimum Spanning Tree)的问题的经典算法,它采用贪心策略,基本思想是每次选择权重最小的边,并且该边不会形成回路。算法使用并查集(Union-Find)数据结构来检测是否会形成回路。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 边结构体
struct Edge {
    int u, v, weight;
    
    // 重载运算符,按边的权重排序
    bool operator<(const Edge &e) const {
        return weight < e.weight;
    }
};

// 并查集结构体
struct DisjointSet {
    vector<int> parent, rank;
    
    DisjointSet(int n) {
        parent.resize(n);
        rank.resize(n, 0);
        for (int i = 0; i < n; i++) {
            parent[i] = i;
        }
    }
    
    // 查找根节点,路径压缩
    int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);
        }
        return parent[x];
    }
    
    // 合并两个集合,按秩合并
    bool unionSets(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        
        if (rootX != rootY) {
            if (rank[rootX] > rank[rootY]) {
                parent[rootY] = rootX;
            } else if (rank[rootX] < rank[rootY]) {
                parent[rootX] = rootY;
            } else {
                parent[rootY] = rootX;
                rank[rootX]++;
            }
            return true;
        }
        return false;
    }
};

// Kruskal 算法
int kruskal(int n, vector<Edge> &edges) {
    // 1. 对所有边按权重进行排序
    sort(edges.begin(), edges.end());
    
    DisjointSet ds(n);
    int mstWeight = 0;
    
    // 2. 遍历所有边,选择不形成回路的边
    for (const Edge &e : edges) {
        if (ds.unionSets(e.u, e.v)) {
            mstWeight += e.weight;  // 加入到最小生成树
        }
    }
    
    return mstWeight;
}

int main() {
    int n, m;
    cout << "Enter number of nodes and edges: ";
    cin >> n >> m;
    
    vector<Edge> edges(m);
    cout << "Enter the edges (u, v, weight):\n";
    for (int i = 0; i < m; i++) {
        cin >> edges[i].u >> edges[i].v >> edges[i].weight;
    }
    
    int mstWeight = kruskal(n, edges);
    
    cout << "Weight of Minimum Spanning Tree: " << mstWeight << endl;
    
    return 0;
}

Enter number of nodes and edges: 4 5
Enter the edges (u, v, weight):
0 1 10
0 2 6
0 3 5
1 3 15
2 3 4
Weight of Minimum Spanning Tree: 19

最小生成数之 Prim 算法(一般用于稠密图)

Prim 算法也是求解最小生成树(MST)的经典算法之一。与 Kruskal 算法不同,Prim 算法是从一个起始点出发,逐步扩展生成树的边,每次选择权重最小的边,将该边加入到生成树中,并连接新的顶点。它使用优先队列(通常是最小堆)来高效地选择最小权重的边。

#include <iostream>
#include <vector>
#include <queue>
#include <climits>

using namespace std;

// 边结构体
struct Edge {
    int to, weight;
    
    // 构造函数
    Edge(int t, int w) : to(t), weight(w) {}
    
    // 重载运算符,用于优先队列按边的权重排序
    bool operator>(const Edge& e) const {
        return weight > e.weight;
    }
};

// Prim 算法
int prim(int n, const vector<vector<Edge>>& graph) {
    vector<bool> inMST(n, false);  // 记录每个节点是否已被加入生成树
    priority_queue<Edge, vector<Edge>, greater<Edge>> pq;  // 最小堆,按边权重升序排列
    int mstWeight = 0;
    
    // 从第一个节点开始
    pq.push(Edge(0, 0));  // 初始节点的权重为 0
    while (!pq.empty()) {
        Edge e = pq.top();
        pq.pop();
        
        int u = e.to;
        
        // 如果节点 u 已在生成树中,跳过
        if (inMST[u]) continue;
        
        // 将节点 u 加入生成树
        inMST[u] = true;
        mstWeight += e.weight;
        
        // 遍历 u 的邻接边
        for (const Edge& next : graph[u]) {
            if (!inMST[next.to]) {  // 如果邻接点不在生成树中,加入优先队列
                pq.push(next);
            }
        }
    }
    
    return mstWeight;
}

int main() {
    int n, m;
    cout << "Enter number of nodes and edges: ";
    cin >> n >> m;
    
    // 邻接表表示图
    vector<vector<Edge>> graph(n);
    cout << "Enter the edges (u, v, weight):\n";
    for (int i = 0; i < m; i++) {
        int u, v, weight;
        cin >> u >> v >> weight;
        graph[u].push_back(Edge(v, weight));
        graph[v].push_back(Edge(u, weight));  // 无向图,双向加边
    }
    
    int mstWeight = prim(n, graph);
    
    cout << "Weight of Minimum Spanning Tree: " << mstWeight << endl;
    
    return 0;
}

欧拉路径

#include <iostream>
#include <vector>
#include <stack>

using namespace std;

class EulerianPath {
private:
    int V; // 顶点数
    vector<vector<int>> adj; // 邻接表
    vector<int> degree; // 记录度数
    vector<vector<bool>> visited; // 记录边是否被访问

public:
    EulerianPath(int vertices) : V(vertices), adj(vertices), degree(vertices, 0), visited(vertices, vector<bool>(vertices, false)) {}

    void addEdge(int u, int v) {
        adj[u].push_back(v);
        adj[v].push_back(u);
        degree[u]++;
        degree[v]++;
    }

    bool isEulerianPath() {
        int oddCount = 0;
        for (int i = 0; i < V; i++) {
            if (degree[i] % 2 == 1) oddCount++;
        }
        return (oddCount == 0 || oddCount == 2);
    }

    void findEulerianPath() {
        if (!isEulerianPath()) {
            cout << "此图无欧拉路径!" << endl;
            return;
        }

        int start = 0;
        for (int i = 0; i < V; i++) {
            if (degree[i] % 2 == 1) { // 找到奇度点作为起点
                start = i;
                break;
            }
        }

        stack<int> stk;
        vector<int> path;
        stk.push(start);

        while (!stk.empty()) {
            int u = stk.top();
            bool found = false;
            for (int i = 0; i < adj[u].size(); i++) {
                int v = adj[u][i];
                if (!visited[u][v]) {
                    visited[u][v] = visited[v][u] = true;
                    stk.push(v);
                    found = true;
                    break;
                }
            }
            if (!found) {
                path.push_back(u);
                stk.pop();
            }
        }

        cout << "欧拉路径:";
        for (int i = path.size() - 1; i >= 0; i--) {
            cout << path[i] << (i == 0 ? "\n" : " -> ");
        }
    }
};

int main() {
    EulerianPath graph(5);
    graph.addEdge(0, 1);
    graph.addEdge(1, 2);
    graph.addEdge(2, 0);
    graph.addEdge(1, 3);
    graph.addEdge(3, 4);
    graph.addEdge(4, 1);

    graph.findEulerianPath();

    return 0;
}

欧拉密码字典

class ODic {  
private:  
    unordered_set<int> seen;  
    string ans;  
    ll highest;  
    int k;  
  
public:  
    void dfs(int node) {  
        for (int x = 0; x < k; ++x) {  
            int nei = node * 26 + x;  
            if (!seen.count(nei)) {  
                seen.insert(nei);  
                dfs(nei % highest);  
                ans += (x + 'a');  
            }  
        }  
    }  
  
    string gen(int n, int k) {  
        highest = pow(26, n - 1);  
        this->k = k;  
        dfs(0);  
        ans += string(n - 1, 'a');  
        return ans;  
    }  
};
class Solution {
private:
    unordered_set<int> seen;
    string ans;
    int highest;
    int k;

public:
    void dfs(int node) {
        for (int x = 0; x < k; ++x) {
            int nei = node * 10 + x;
            if (!seen.count(nei)) {
                seen.insert(nei);
                dfs(nei % highest);
                ans += (x + '0');
            }
        }
    }

    string crackSafe(int n, int k) {
        highest = pow(10, n - 1);
        this->k = k;
        dfs(0);
        ans += string(n - 1, '0');
        return ans;
    }
};

数论

bool is_prime(int n) {  
    if (n == 2) return true;  
    if (!(n&1)) return false;  
    for (int i = 3; i * i <= n; i += 2) {  
        if (n % i == 0) {  
            return false;  
        }  
    }  
    return n > 2;  
}

快速幂

#include <iostream>

long long qpow_iterative(long long base, long long exp, long long mod) {
    long long result = 1;
    while (exp > 0) {
        if (exp % 2) result = (result * base) % mod;
        base = (base * base) % mod;
        exp /= 2;
    }
    return result;
}

int main() {
    long long base = 2, exp = 10, mod = 1000000007;
    std::cout << "2^10 % 1000000007 = " << qpow_iterative(base, exp, mod) << std::endl;
    return 0;
}

埃拉托斯特尼筛法

// 埃拉托斯特尼筛法,生成所有不超过 n 的素数  
vector<int> getPrimes(int n) {  
    vector<bool> isPrime(n + 1, true);  
    isPrime[0] = isPrime[1] = false;  
    for (int i = 2; i * i <= n; i++) {  
        if(isPrime[i])  
            for (int j = i * i; j <= n; j += i)  
                isPrime[j] = false;  
    }  
    vector<int> primes;  
    for (int i = 2; i <= n; i++) {  
        if(isPrime[i]) {  
            primes.push_back(i);  
        }  
    }  
    return primes;  
}
const int MX = 1e6;
vector<int> primes;
int init = []() {
    bool np[MX + 1]{};
    for (int i = 2; i <= MX; ++i) {
        if (!np[i]) primes.push_back(i);
        for (int p: primes) {
            if (i * p > MX) break;
            np[i * p] = true;
            if (i % p == 0) break;
        }
    }
    primes.push_back(MX + 1);
    primes.push_back(MX + 1); // 保证下面下标不会越界
    return 0;
}();

回文素数库

nums=[2, 3, 5, 7, 11, 101, 131, 151, 181, 191, 313, 353, 373, 383, 727, 757, 787, 797, 919, 929, 10301, 10501, 10601, 11311, 11411, 12421, 12721, 12821, 13331, 13831, 13931, 14341, 14741, 15451, 15551, 16061, 16361, 16561, 16661, 17471, 17971, 18181, 18481, 19391, 19891, 19991, 30103, 30203, 30403, 30703, 30803, 31013, 31513, 32323, 32423, 33533, 34543, 34843, 35053, 35153, 35353, 35753, 36263, 36563, 37273, 37573, 38083, 38183, 38783, 39293, 70207, 70507, 70607, 71317, 71917, 72227, 72727, 73037, 73237, 73637, 74047, 74747, 75557, 76367, 76667, 77377, 77477, 77977, 78487, 78787, 78887, 79397, 79697, 79997, 90709, 91019, 93139, 93239, 93739, 94049, 94349, 94649, 94849, 94949, 95959, 96269, 96469, 96769, 97379, 97579, 97879, 98389, 98689, 1003001, 1008001, 1022201, 1028201, 1035301, 1043401, 1055501, 1062601, 1065601, 1074701, 1082801, 1085801, 1092901, 1093901, 1114111, 1117111, 1120211, 1123211, 1126211, 1129211, 1134311, 1145411, 1150511, 1153511, 1160611, 1163611, 1175711, 1177711, 1178711, 1180811, 1183811, 1186811, 1190911, 1193911, 1196911, 1201021, 1208021, 1212121, 1215121, 1218121, 1221221, 1235321, 1242421, 1243421, 1245421, 1250521, 1253521, 1257521, 1262621, 1268621, 1273721, 1276721, 1278721, 1280821, 1281821, 1286821, 1287821, 1300031, 1303031, 1311131, 1317131, 1327231, 1328231, 1333331, 1335331, 1338331, 1343431, 1360631, 1362631, 1363631, 1371731, 1374731, 1390931, 1407041, 1409041, 1411141, 1412141, 1422241, 1437341, 1444441, 1447441, 1452541, 1456541, 1461641, 1463641, 1464641, 1469641, 1486841, 1489841, 1490941, 1496941, 1508051, 1513151, 1520251, 1532351, 1535351, 1542451, 1548451, 1550551, 1551551, 1556551, 1557551, 1565651, 1572751, 1579751, 1580851, 1583851, 1589851, 1594951, 1597951, 1598951, 1600061, 1609061, 1611161, 1616161, 1628261, 1630361, 1633361, 1640461, 1643461, 1646461, 1654561, 1657561, 1658561, 1660661, 1670761, 1684861, 1685861, 1688861, 1695961, 1703071, 1707071, 1712171, 1714171, 1730371, 1734371, 1737371, 1748471, 1755571, 1761671, 1764671, 1777771, 1793971, 1802081, 1805081, 1820281, 1823281, 1824281, 1826281, 1829281, 1831381, 1832381, 1842481, 1851581, 1853581, 1856581, 1865681, 1876781, 1878781, 1879781, 1880881, 1881881, 1883881, 1884881, 1895981, 1903091, 1908091, 1909091, 1917191, 1924291, 1930391, 1936391, 1941491, 1951591, 1952591, 1957591, 1958591, 1963691, 1968691, 1969691, 1970791, 1976791, 1981891, 1982891, 1984891, 1987891, 1988891, 1993991, 1995991, 1998991, 3001003, 3002003, 3007003, 3016103, 3026203, 3064603, 3065603, 3072703, 3073703, 3075703, 3083803, 3089803, 3091903, 3095903, 3103013, 3106013, 3127213, 3135313, 3140413, 3155513, 3158513, 3160613, 3166613, 3181813, 3187813, 3193913, 3196913, 3198913, 3211123, 3212123, 3218123, 3222223, 3223223, 3228223, 3233323, 3236323, 3241423, 3245423, 3252523, 3256523, 3258523, 3260623, 3267623, 3272723, 3283823, 3285823, 3286823, 3288823, 3291923, 3293923, 3304033, 3305033, 3307033, 3310133, 3315133, 3319133, 3321233, 3329233, 3331333, 3337333, 3343433, 3353533, 3362633, 3364633, 3365633, 3368633, 3380833, 3391933, 3392933, 3400043, 3411143, 3417143, 3424243, 3425243, 3427243, 3439343, 3441443, 3443443, 3444443, 3447443, 3449443, 3452543, 3460643, 3466643, 3470743, 3479743, 3485843, 3487843, 3503053, 3515153, 3517153, 3528253, 3541453, 3553553, 3558553, 3563653, 3569653, 3586853, 3589853, 3590953, 3591953, 3594953, 3601063, 3607063, 3618163, 3621263, 3627263, 3635363, 3643463, 3646463, 3670763, 3673763, 3680863, 3689863, 3698963, 3708073, 3709073, 3716173, 3717173, 3721273, 3722273, 3728273, 3732373, 3743473, 3746473, 3762673, 3763673, 3765673, 3768673, 3769673, 3773773, 3774773, 3781873, 3784873, 3792973, 3793973, 3799973, 3804083, 3806083, 3812183, 3814183, 3826283, 3829283, 3836383, 3842483, 3853583, 3858583, 3863683, 3864683, 3867683, 3869683, 3871783, 3878783, 3893983, 3899983, 3913193, 3916193, 3918193, 3924293, 3927293, 3931393, 3938393, 3942493, 3946493, 3948493, 3964693, 3970793, 3983893, 3991993, 3994993, 3997993, 3998993, 7014107, 7035307, 7036307, 7041407, 7046407, 7057507, 7065607, 7069607, 7073707, 7079707, 7082807, 7084807, 7087807, 7093907, 7096907, 7100017, 7114117, 7115117, 7118117, 7129217, 7134317, 7136317, 7141417, 7145417, 7155517, 7156517, 7158517, 7159517, 7177717, 7190917, 7194917, 7215127, 7226227, 7246427, 7249427, 7250527, 7256527, 7257527, 7261627, 7267627, 7276727, 7278727, 7291927, 7300037, 7302037, 7310137, 7314137, 7324237, 7327237, 7347437, 7352537, 7354537, 7362637, 7365637, 7381837, 7388837, 7392937, 7401047, 7403047, 7409047, 7415147, 7434347, 7436347, 7439347, 7452547, 7461647, 7466647, 7472747, 7475747, 7485847, 7486847, 7489847, 7493947, 7507057, 7508057, 7518157, 7519157, 7521257, 7527257, 7540457, 7562657, 7564657, 7576757, 7586857, 7592957, 7594957, 7600067, 7611167, 7619167, 7622267, 7630367, 7632367, 7644467, 7654567, 7662667, 7665667, 7666667, 7668667, 7669667, 7674767, 7681867, 7690967, 7693967, 7696967, 7715177, 7718177, 7722277, 7729277, 7733377, 7742477, 7747477, 7750577, 7758577, 7764677, 7772777, 7774777, 7778777, 7782877, 7783877, 7791977, 7794977, 7807087, 7819187, 7820287, 7821287, 7831387, 7832387, 7838387, 7843487, 7850587, 7856587, 7865687, 7867687, 7868687, 7873787, 7884887, 7891987, 7897987, 7913197, 7916197, 7930397, 7933397, 7935397, 7938397, 7941497, 7943497, 7949497, 7957597, 7958597, 7960697, 7977797, 7984897, 7985897, 7987897, 7996997, 9002009, 9015109, 9024209, 9037309, 9042409, 9043409, 9045409, 9046409, 9049409, 9067609, 9073709, 9076709, 9078709, 9091909, 9095909, 9103019, 9109019, 9110119, 9127219, 9128219, 9136319, 9149419, 9169619, 9173719, 9174719, 9179719, 9185819, 9196919, 9199919, 9200029, 9209029, 9212129, 9217129, 9222229, 9223229, 9230329, 9231329, 9255529, 9269629, 9271729, 9277729, 9280829, 9286829, 9289829, 9318139, 9320239, 9324239, 9329239, 9332339, 9338339, 9351539, 9357539, 9375739, 9384839, 9397939, 9400049, 9414149, 9419149, 9433349, 9439349, 9440449, 9446449, 9451549, 9470749, 9477749, 9492949, 9493949, 9495949, 9504059, 9514159, 9526259, 9529259, 9547459, 9556559, 9558559, 9561659, 9577759, 9583859, 9585859, 9586859, 9601069, 9602069, 9604069, 9610169, 9620269, 9624269, 9626269, 9632369, 9634369, 9645469, 9650569, 9657569, 9670769, 9686869, 9700079, 9709079, 9711179, 9714179, 9724279, 9727279, 9732379, 9733379, 9743479, 9749479, 9752579, 9754579, 9758579, 9762679, 9770779, 9776779, 9779779, 9781879, 9782879, 9787879, 9788879, 9795979, 9801089, 9807089, 9809089, 9817189, 9818189, 9820289, 9822289, 9836389, 9837389, 9845489, 9852589, 9871789, 9888889, 9889889, 9896989, 9902099, 9907099, 9908099, 9916199, 9918199, 9919199, 9921299, 9923299, 9926299, 9927299, 9931399, 9932399, 9935399, 9938399, 9957599, 9965699, 9978799, 9980899, 9981899, 9989899, 100030001, 100050001, 100060001, 100111001, 100131001, 100161001, 100404001, 100656001, 100707001, 100767001, 100888001, 100999001, 101030101, 101060101, 101141101, 101171101, 101282101, 101292101, 101343101, 101373101, 101414101, 101424101, 101474101, 101595101, 101616101, 101717101, 101777101, 101838101, 101898101, 101919101, 101949101, 101999101, 102040201, 102070201, 102202201, 102232201, 102272201, 102343201, 102383201, 102454201, 102484201, 102515201, 102676201, 102686201, 102707201, 102808201, 102838201, 103000301, 103060301, 103161301, 103212301, 103282301, 103303301, 103323301, 103333301, 103363301, 103464301, 103515301, 103575301, 103696301, 103777301, 103818301, 103828301, 103909301, 103939301, 104000401, 104030401, 104040401, 104111401, 104222401, 104282401, 104333401, 104585401, 104616401, 104787401, 104838401, 104919401, 104949401, 105121501, 105191501, 105202501, 105262501, 105272501, 105313501, 105323501, 105343501, 105575501, 105616501, 105656501, 105757501, 105818501, 105868501, 105929501, 106060601, 106111601, 106131601]

gcd()

C++自带的gcd

#include <numeric>
using namespace std;

gcd(a,b);

递归写法的cgd

int my_gcd(int a, int b) {  
    if (!b) return a;  
    if (a % b) return my_gcd(b, a % b);  
    return b;  
}

非递归

int my_gcd(int a, int b) {
    while (b != 0) {
        int remainder = a % b;
        a = b;
        b = remainder;
    }
    return a;
}

扩展gcd()

#include <iostream>

// 扩展欧几里得算法
// 计算 ax + by = gcd(a, b) 的解 (x, y)
int extended_gcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1;
        y = 0;
        return a; // gcd(a, 0) = a
    }
    int x1, y1;
    int gcd = extended_gcd(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return gcd;
}

int main() {
    int a, b, x, y;
    std::cout << "输入两个整数 a 和 b: ";
    std::cin >> a >> b;
    
    int gcd = extended_gcd(a, b, x, y);
    
    std::cout << "gcd(" << a << ", " << b << ") = " << gcd << std::endl;
    std::cout << "系数 x = " << x << ", y = " << y << std::endl;
    
    return 0;
}

Lucas定理

#include <iostream>
#include <vector>

using namespace std;

// 扩展欧几里得算法,求模逆元
int mod_inverse(int a, int m) {
    int m0 = m, t, q;
    int x0 = 0, x1 = 1;
    if (m == 1) return 0;
    while (a > 1) {
        // q是商,t是余数
        q = a / m;
        t = m;
        
        // m是余数
        m = a % m;
        a = t;
        
        // 更新x0和x1
        t = x0;
        x0 = x1 - q * x0;
        x1 = t;
    }
    
    // 使得x1成为正数
    if (x1 < 0) x1 += m0;
    
    return x1;
}

// 计算n! % p
int factorial_mod(int n, int p) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result = (result * i) % p;
    }
    return result;
}

// 计算C(n, k) % p
int binomial_mod(int n, int k, int p) {
    if (k > n) return 0;
    
    // 计算n! % p, k! % p 和 (n-k)! % p
    int numerator = factorial_mod(n, p);
    int denominator = (factorial_mod(k, p) * factorial_mod(n - k, p)) % p;
    
    // 使用模逆元
    int denominator_inv = mod_inverse(denominator, p);
    
    return (numerator * denominator_inv) % p;
}

// 根据卢卡斯定理计算C(n, k) % p
int lucas_theorem(int n, int k, int p) {
    int result = 1;
    
    // 使用卢卡斯定理:将n和k表示成p进制
    while (n > 0 || k > 0) {
        int n_i = n % p;
        int k_i = k % p;
        
        // 计算 C(n_i, k_i) % p
        result = (result * binomial_mod(n_i, k_i, p)) % p;
        
        // 更新n和k
        n /= p;
        k /= p;
    }
    
    return result;
}

int main() {
    int n, k, p;
    cout << "Enter n, k, and p (prime): ";
    cin >> n >> k >> p;

    int result = lucas_theorem(n, k, p);
    cout << "C(" << n << ", " << k << ") mod " << p << " = " << result << endl;

    return 0;
}

费马定理

求1的逆元

在模 ppp 意义下求整数 aaa 的逆元(即找到 x 使得 $a \times x ≡ 1 (mod .p)$),通常有两种方法:

  1. 扩展欧几里得算法(适用于一般模数)
  2. 费马小定理(适用于模数是质数的情况)

方法 1:扩展欧几里得算法求逆元

适用于任意模 p,但要求 a 与 p 互质(即 $gcd⁡(a,p)=1$)。

#include <iostream>

// 扩展欧几里得算法
long long exgcd(long long a, long long b, long long &x, long long &y) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    long long x1, y1;
    long long g = exgcd(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return g;
}

// 求 a 在 mod p 下的逆元
long long mod_inv(long long a, long long p) {
    long long x, y;
    long long g = exgcd(a, p, x, y);
    if (g != 1) {
        std::cerr << "无解,逆元不存在\n";
        return -1;
    }
    return (x % p + p) % p; // 确保结果非负
}

int main() {
    long long a, p;
    std::cout << "输入 a 和 p (p 需要是一个正整数): ";
    std::cin >> a >> p;
    long long inv = mod_inv(a, p);
    if (inv != -1)
        std::cout << "逆元: " << inv << std::endl;
    return 0;
}

方法 2:费马小定理

适用于模 p 为 质数 的情况,根据费马小定理:

#include <iostream>

// 快速幂计算 (a^b) % mod
long long fast_pow(long long a, long long b, long long mod) {
    long long res = 1;
    while (b > 0) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

// 费马小定理求逆元 (仅适用于 p 为质数)
long long mod_inv(long long a, long long p) {
    return fast_pow(a, p - 2, p);
}

int main() {
    long long a, p;
    std::cout << "输入 a 和 p (p 必须是质数): ";
    std::cin >> a >> p;
    std::cout << "逆元: " << mod_inv(a, p) << std::endl;
    return 0;
}

求 $\frac{b}{a}%m$

#include <iostream>

// 扩展欧几里得算法求 ax + by = gcd(a, b) 的整数解 (x, y)
long long exgcd(long long a, long long b, long long &x, long long &y) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    long long x1, y1;
    long long g = exgcd(b, a % b, x1, y1);
    x = y1;
    y = x1 - (a / b) * y1;
    return g;
}

// 求解 ax ≡ b (mod p)
bool solve_mod_equation(long long a, long long b, long long p, long long &x) {
    long long x0, y;
    long long g = exgcd(a, p, x0, y); // 求 a 关于 p 的逆元
    if (b % g != 0) { // 若 b 不是 gcd(a, p) 的倍数,则无解
        return false;
    }
    // 计算 x0 * (b / g) 模 (p / g)
    x = ((x0 * (b / g)) % (p / g) + (p / g)) % (p / g);
    return true;
}

int main() {
    long long a, b, p, x;
    std::cout << "输入 a, b, p: ";
    std::cin >> a >> b >> p;
    
    if (solve_mod_equation(a, b, p, x)) {
        std::cout << "方程的最小正整数解: " << x << std::endl;
    } else {
        std::cout << "无解\n";
    }

    return 0;
}

当模数 ppp 为质数且 gcd⁡(a,p)=1时,可以使用费马小定理结合快速幂来求解方程:

#include <iostream>

// 快速幂计算 (a^b) % mod
long long fast_pow(long long a, long long b, long long mod) {
    long long res = 1;
    while (b > 0) {
        if (b & 1) res = res * a % mod; // 如果当前位为1
        a = a * a % mod; // 平方底数
        b >>= 1; // 移位
    }
    return res;
}

// 使用快速幂求解 ax ≡ b (mod p)
long long solve_mod_equation(long long a, long long b, long long p) {
    long long inv_a = fast_pow(a, p - 2, p); // 计算 a^(-1) mod p
    return (b * inv_a) % p; // 计算 x ≡ b * a^(-1) (mod p)
}

int main() {
    long long a, b, p;
    std::cout << "输入 a, b, p (p 必须是质数): ";
    std::cin >> a >> b >> p;

    if (std::__gcd(a, p) != 1) { // 确保 a 在 mod p 下有逆元
        std::cout << "无解(a 和 p 不是互质的)\n";
        return 0;
    }

    long long x = solve_mod_equation(a, b, p);
    std::cout << "方程的最小正整数解: " << x << std::endl;
    return 0;
}

KMP

KMP算法用于在一个文本中寻找一个模式串的位置。它通过预处理模式串来避免匹配失败时的回溯,从而提升搜索效率。

#include <iostream>
#include <vector>
#include <string>

// 计算部分匹配表(也叫前缀表)
std::vector<int> computePrefixFunction(const std::string& pattern) {
    int m = pattern.length();
    std::vector<int> prefix(m, 0);
    int j = 0;  // 记录前缀的长度

    for (int i = 1; i < m; ++i) {
        // 匹配失败时回溯
        while (j > 0 && pattern[i] != pattern[j]) {
            j = prefix[j - 1];
        }
        
        if (pattern[i] == pattern[j]) {
            ++j;
        }
        
        prefix[i] = j;
    }
    
    return prefix;
}

// KMP算法进行模式匹配
int KMP(const std::string& text, const std::string& pattern) {
    int n = text.length();
    int m = pattern.length();
    
    if (m == 0) return 0;  // 空模式串的特殊情况
    
    std::vector<int> prefix = computePrefixFunction(pattern);
    
    int j = 0;  // 用于模式串的匹配
    for (int i = 0; i < n; ++i) {
        // 匹配失败时回溯
        while (j > 0 && text[i] != pattern[j]) {
            j = prefix[j - 1];
        }
        
        if (text[i] == pattern[j]) {
            ++j;
        }
        
        // 完全匹配时返回匹配的起始位置
        if (j == m) {
            return i - m + 1;  // 匹配成功,返回起始位置
        }
    }
    
    return -1;  // 匹配失败
}

int main() {
    std::string text, pattern;
    
    std::cout << "Enter the text: ";
    std::cin >> text;
    
    std::cout << "Enter the pattern: ";
    std::cin >> pattern;
    
    int position = KMP(text, pattern);
    
    if (position != -1) {
        std::cout << "Pattern found at index " << position << std::endl;
    } else {
        std::cout << "Pattern not found" << std::endl;
    }
    
    return 0;
}

/**  
 * KMP算法  
 * @param text 文本字符串  
 * @param pattern 要匹配的字符串  
 * @return 下标位置  
 */    
int kmp(const string &text, const string &pattern) {  
	int m = (int) pattern.length();  
	if (m == 0) return 0;  // 空模式串的特殊情况  

	/*  
	 * 计算部分匹配表(前缀表)  
	 * 即首先需要获取一个查询数组prefix,代表pattern前i个字符组成的字符串它的最长相同前后缀 的长度。  
	 *  例如 pattern="ABXABM",那么prefix[3]=1, prefix[4]=2。  
	 *  这个过程其实直接暴力计算也无妨,但是这里还是做了小优化。这里用的是对称映射法。  
	 *   j是前缀的长度,在这里可以作为最长相同前后缀下标,也可以作为最长相同前后缀的长度。  
	 *   规定:对于像pattern="AAAA", prefix = [0,1,2,3],此时j=3。因为规定这里的前后缀是不包括本身的。  
	 *   如果"AAAAB",此时patter[i] != pattern[j],考虑前一个前缀(即前缀长度减一),...,最终prefix = [0,1,2,0]  
	 *   如果"AAAABA",上一个最长前后缀长度是0,这个字符如果和patter[0]相等,那么这里的最长前缀长度为0+1=1,  
	 *    也就是说如果上一个最长前后缀长度是r,其实只要看这个字符串和pattern[r]是否相等就可以了。因为我们是要计算的是前后缀,不是回文,像"ABCXABC",patter[6]=3  
	 */        
	vecI prefix(m, 0);  // 计算部分匹配表(前缀表)  
	int j = 0;  // 记录前缀的长度  
	for (int i = 1; i < m; ++i) {  
		// 匹配失败时回溯  
		while (j > 0 and pattern[i] != pattern[j]) j = prefix[j - 1];  
		// 匹配相等长度是上一个+1  
		if (pattern[i] == pattern[j]) prefix[i] = ++j;  
	}  
//        prefix;  

	/*         * KMP算法进行模式匹配  
	 */        int n = (int)text.length();  
	j = 0;  // 匹配相等的长度  
	for (int i = 0; i < n; ++i) {  
		/*  
		 * 匹配失败时回溯  
		 * 这时候pattern的最长相等前后缀查询数组就可以用于跳过pattern中的下标  
		 * 例如text="XXABCDABXXXX", pattern="ABCDABD",当匹配到text[8]!=pattern[6]时,发现prefix[5]=2,于是就可以从text[9]?=pattern[2]开始比较  
		 *             */            while (j > 0 && text[i] != pattern[j]) j = prefix[j - 1];  
		if (text[i] == pattern[j]) ++j;  
		if (j == m) return i - m + 1;  
	}  
	return -1;  // 匹配失败  
}

Z函数

Z[i]表示字符串 S 的后缀 S[i:]与 S 的最长公共前缀(LCP)的长度。
示例
字符串: S = "abacaba"

索引 i0123456
S[i]abacaba
Z[i]7010301
vector<ll> compute_z(const string &s) {  
    size_t n = s.length();  
    vector<ll> z(n);  
    for (int i = 0, l = 0, r = 0; i < n; ++i) {  
        z[i] = max(min(z[i - l], r - i + 1ll), 0ll);  
        while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {  
            l = i;  
            r = i + z[i];  
            ++z[i];  
        }  
    }  
    return z;  
}

回文子串

实现回文字串 查询数组

vector cost(n, vector(n, 0));  // cost[i][j] 表示将s[i..j]变为回文子串所需的操作次数,这里取的s下标是从0开始  
for (int span = 2; span <= n; ++span) {  
    for (int i = 0; i <= n - span; ++i) {  
        int j = i + span - 1;  
        cost[i][j] = cost[i + 1][j - 1] + (s[i] != s[j]);  
    }  
}

如果要查询s[i..j]是否是回文子串,只需cost[i][j] == 0

Manacher算法

Manacher 算法可以计算以 s[i](或者s[i]s[i+1])为回文中心的最长回文子串的长度。

// 预处理字符串,插入分隔符 '#',避免奇偶分类  
string preprocess(const string &s) {  
    string t = "#";  
    for (char c : s) {  
        t += c;  
        t += "#";  
    }  
    return t;  
}  
  
// Manacher 算法求最长回文子串长度  
int manacher(const string &s) {  
    string t = preprocess(s);   // "babad" => "#b#a#b#a#d#"  
    int len = t.size();  
    vector<int> p(len, 0); // $p[i]+1$ 表示以 t[i] 为中心的回文半径  
    int center = 0, right = 0, maxLen = 0;  
  
    for (int i = 0; i < len; i++) {  
        int mirr = 2 * center - i; // i 关于 center 的对称点  
  
        if (i < right) {  
            p[i] = min(p[mirr], right - i);  
        }  
  
        // 尝试扩展回文  
        while (i - p[i] >= 0 && i + p[i] < len && t[i - p[i]] == t[i + p[i]]) {  
            p[i]++;  
        }  
  
        // 更新 center 和 right        
        if (i + p[i] > right) {  
            center = i;  
            right = i + p[i];  
        }  
  
        // 记录最大回文长度  
        maxLen = max(maxLen, p[i] - 1);  
    }  
  
    return maxLen;  
}

最长回文子序列(LPS)

#include <iostream>
#include <vector>
#include <string>

int longestPalindromeSubseq(const std::string& s) {
    int n = s.size();
    if (n == 0) return 0;

    // dp[i][j] 表示 s[i...j] 的最长回文子序列长度
    std::vector<std::vector<int>> dp(n, std::vector<int>(n, 0));

    // 初始化:单个字符的最长回文子序列长度为 1
    for (int i = 0; i < n; ++i) {
        dp[i][i] = 1;
    }

    // 逐步扩大子串长度
    for (int len = 2; len <= n; ++len) {  // len 是子串的长度
        for (int i = 0; i <= n - len; ++i) {
            int j = i + len - 1;
            if (s[i] == s[j]) {
                dp[i][j] = dp[i + 1][j - 1] + 2;
            } else {
                dp[i][j] = std::max(dp[i + 1][j], dp[i][j - 1]);
            }
        }
    }

    return dp[0][n - 1]; // 整个字符串的最长回文子序列长度
}

int main() {
    std::string s = "bbbab";
    std::cout << "最长回文子序列长度: " << longestPalindromeSubseq(s) << std::endl;
    return 0;
}
#include <iostream>
#include <vector>
#include <string>

int longestPalindromeSubseq(const std::string& s) {
    int n = s.size();
    if (n == 0) return 0;

    std::vector<int> prev(n, 0), curr(n, 0);

    for (int i = n - 1; i >= 0; --i) {
        curr[i] = 1; // 单个字符的回文子序列长度为 1
        for (int j = i + 1; j < n; ++j) {
            if (s[i] == s[j]) {
                curr[j] = prev[j - 1] + 2;
            } else {
                curr[j] = std::max(prev[j], curr[j - 1]);
            }
        }
        prev = curr; // 更新滚动数组
    }

    return curr[n - 1];
}

int main() {
    std::string s = "bbbab";
    std::cout << "最长回文子序列长度: " << longestPalindromeSubseq(s) << std::endl;
    return 0;
}

字符串哈希

/**  
 * 长度为k的子串哈希  
 * @param s 字符串  
 * @param k 子串长度  
 * @return 所有长度为k的子串的哈希  
 */vector<long long> getSubstrHash(const string& s, long long k) {  
    /*  
     * 这里使用的是 Rabin-Karp 滚动哈希 法 (基于定长滑动窗口法)  
     */    const long long base = 13331; // or131 选择一个大质数作为哈希基数  
    const long long mod = 1e9 + 7; // 选择一个大素数取模,减少哈希冲突  
    vector<long long> substrHash;  
    long long hash = 0, power = 1;  
  
    if (s.size() < k) return substrHash; // 如果字符串长度小于 k,返回空数组  
  
    // 计算第一个长度为 k 的子串哈希值  
    for (long long i = 0; i < k; ++i) {  
        hash = (hash * base + s[i]) % mod;  
        if (i > 0) power = (power * base) % mod; // 预计算 base^(k-1)    }  
    substrHash.push_back(hash);  
  
    // 滚动哈希计算后续子串的哈希值  
    for (long long i = k; i < s.size(); ++i) {  
        hash = (hash - s[i - k] * power % mod + mod) % mod; // 移除旧字符  
        hash = (hash * base + s[i]) % mod; // 添加新字符  
        substrHash.push_back(hash);  
    }  
  
    return substrHash;  
}

int main() {
    string s = "abcdefg";
    long long k = 3;
    vector<long long> hashes = getSubstrHash(s, k);

    cout << "所有长度为 " << k << " 的子串哈希值:" << endl;
    for (long long h : hashes) {
        cout << h << " ";
    }
    cout << endl;

    return 0;
}
/**  
 * 字符串哈希也是迭代哈希的一种。  
 *  对于字符串s其哈希迭代公式是:  
 *      首先需要给定一个哈希系数p和一个模值m。  
 *      $sum(s[i] * (p ** i)) % m$ 。(p**i)是p的i次方  
 **/
class Solution {  
public:  
    /**  
     * 2156. 查找给定哈希值的子串  
     * 找出s中第一个 长度为k,哈希值是hashValue的子串。  
     *  对于s[i]: 'a' = 1, 'b'=2, ...。i从0开始  
     * @param s     * @param power 哈希系数p(幂次方系数)  
     * @param modulo 模值  
     * @param k     * @param hashValue     * @return     */    string subStrHash(string s, int power, int modulo, int k, int hashValue) {  
        int n = (int) s.size();  
        // s[n-k..] 的哈希值,和 power ** k        ll h = 0, pk = 1;  
        for (int i = n - k; i < n; ++i) {  
            h = (h + (s[i] - 'a' + 1) * pk) % modulo;  
            pk = pk * power % modulo;  
        }  
        int ans = h == hashValue ? n - k : -1;  
        // 向左滑窗  
        for (int i = n - k - 1; i >= 0; --i) {  
            // 细节:pk * (s[i + k] - 'a' + 1) 可能很大,超过模值,因此需要取模一下。  
            // 由于C++模是含符号的,因此就算是`(- pk * (s[i + k] - 'a' + 1)) % modulo`也是负数,需要使用补模法:$(x + modulo) % modulo$  
            // 这里的modulo < max_ll / 2 ,可以直接补模。如果不是的话要加一个if判断。  
            /*h = (h * power + (s[i] - 'a' + 1) - pk * (s[i + k] - 'a' + 1) % modulo);  
            h = (h < 0 ? h + modulo : h) % modulo;*/            h = (h * power + (s[i] - 'a' + 1) - pk * (s[i + k] - 'a' + 1) % modulo + modulo) % modulo;  
            if (h == hashValue) ans = i;  
        }  
        if (ans != -1) return s.substr(ans, k);  
        return "";  
    }  
};

Trie

#include <iostream>
using namespace std;

struct TrieNode {
    TrieNode* children[26]; // 假设只存储小写字母
    bool isEndOfWord;
    TrieNode() {
        isEndOfWord = false;
        for (int i = 0; i < 26; i++) {
            children[i] = nullptr;
        }
    }
};

class Trie {
public:
    Trie() { root = new TrieNode(); }

    // 插入单词
    void insert(const string& word) {
        TrieNode* node = root;
        for (char c : word) {
            int index = c - 'a';
            if (!node->children[index]) {
                node->children[index] = new TrieNode();
            }
            node = node->children[index];
        }
        node->isEndOfWord = true;
    }

    // 查找单词
    bool search(const string& word) {
        TrieNode* node = findNode(word);
        return node && node->isEndOfWord;
    }

    // 前缀匹配
    // 和查询类似,但不要求匹配到单词结束标志,只需要匹配到前缀即可。
    bool startsWith(const string& prefix) {
        return findNode(prefix) != nullptr;
    }

private:
    TrieNode* root;

    TrieNode* findNode(const string& prefix) {
        TrieNode* node = root;
        for (char c : prefix) {
            int index = c - 'a';
            if (!node->children[index]) return nullptr;
            node = node->children[index];
        }
        return node;
    }
};

int main() {
    Trie trie;
    trie.insert("apple");
    cout << trie.search("apple") << endl;   // 输出 1(存在)
    cout << trie.search("app") << endl;     // 输出 0(不存在)
    cout << trie.startsWith("app") << endl; // 输出 1(前缀匹配成功)
    trie.insert("app");
    cout << trie.search("app") << endl;     // 输出 1(插入后存在)
    return 0;
}

高精度

大数 乘 小数

vector<int> mul(const vector<int> &v, int n) {  
    int _ = 0;  
    vector<int> ret;  
    for (int i : v) {  
        _ += i * n;  
        ret.push_back(_ % 10);  
        _ /= 10;  
    }  
    if (_ != 0) ret.push_back(_);  
    return ret;  
}

自定义宏

struct PRINT {  
    PRINT operator, (const vector<int> &vec) {  
        cout << '[';  
        for (int i = 0; i < vec.size() - 1; ++i) {  
            cout << vec[i] << ' ';  
        }  
        cout << vec.back() << ']';  
        return *this;  
    }  
    PRINT operator, (auto n){  
        cout << n << ' ';  
        return *this;  
    }  
}pt;  
#define myPrintln(...) pt, ##__VA_ARGS__;cout << endl;
#define myPrint pt,  
#define myPrintln(...) myPrint __VA_ARGS__,'\n';  
using namespace std;  
  
class PRINT{  
private:  
    string sep = " ";  
public:  
    PRINT operator, (int n){  
        cout << n << sep;  
        return *this;  
    }  
    PRINT operator, (char c){  
        cout << c << sep;  
        return *this;  
    }  
    PRINT operator, (const string &s){  
        cout << s << sep;  
        return *this;  
    }  
    PRINT operator, (double f){  
        cout << f << sep;  
        return *this;  
    }  
}pt;  
  
int main(){  
    int a=100;  
    double b = 500.165;  
    myPrintln(100, 10, 15, a, b)
	// 100 10 15 100 500.165
	return 0;  
}
// #define int long long

#ifndef MOD
#define MOD 1e9+7
class Num {
private:
    static const int mod = MOD;
    int val = 0;

protected:
    static inline int norm(int x) {
        if (x < 0) {
            int cnt = abs(x)/mod + 1;
            x += mod*cnt;
        }
        return x%mod;
    }

    static int binPow(int base, int expo, int p) {
        if (expo == 0) {    // 防止 mod 1
            return 1 % p;
        }
        base %= p;
        int half = binPow(base, expo>>1, p);
        if (expo & 1) {
            return half*half%p * base%p;
        } else {
            return half*half%p;
        }
    }

public:
    Num() {}
    Num(int val):val(norm(val)) {}
    Num(const Num& num) {
        this->val = num.val;
    }

    ~Num() {}

    int getVal() {
        return this->val;
    }
    void setVal(const int val) {
        this->val = norm(val);
    }

    int& operator()() {
        return this->val;
    }

public:
    friend std::istream& operator>>(std::istream& cin, Num& num) {
        cin >> num.val;
        num.val = num.norm(num.val);
        return cin;
    }
    friend std::ostream& operator<<(std::ostream& cout, Num& num) {
        cout << num.val;
        return cout;
    }
    friend std::ostream& operator<<(std::ostream& cout, Num&& num) {
        cout << num.val;
        return cout;
    }

public:
    Num& operator+=(const Num& rhs) {
        val = norm(val + rhs.val);
        return *this;
    }
    Num& operator-=(const Num& rhs) {
        val = norm(val - rhs.val);
        return *this;
    }
    Num& operator*=(const Num& rhs) {
        val = norm(val * rhs.val);
        return *this;
    }
    Num& operator/=(const Num& rhs) {
        int inverseElement = binPow(rhs.val, mod-2, mod);
        val = norm(val * inverseElement);
        return *this;
    }

public:
    Num operator+() {
        Num tmp = val;
        return tmp;
    }
    Num operator-() {
        Num tmp = -val;
        return tmp;
    }

    friend Num operator+(const Num& lhs, const Num& rhs) {
        Num tmp = lhs;
        tmp += rhs;
        return tmp;
    }
    friend Num operator-(const Num& lhs, const Num& rhs) {
        Num tmp = lhs;
        tmp -= rhs;
        return tmp;
    }
    friend Num operator*(const Num& lhs, const Num& rhs) {
        Num tmp = lhs;
        tmp *= rhs;
        return tmp;
    }
    friend Num operator/(const Num& lhs, const Num& rhs) {
        Num tmp = lhs;
        tmp /= rhs;
        return tmp;
    }

public:
    Num operator++(signed) {   // 后置
        Num tmp = *this;
        val = norm(val+1);
        return tmp;
    }
    Num operator--(signed) {   // 后置
        Num tmp = *this;
        val = norm(val-1);
        return tmp;
    }
    Num& operator++() {   // 前置
        val = norm(val+1);
        return *this;
    }
    Num& operator--() {   // 前置
        val = norm(val-1);
        return *this;
    }

public:
    bool operator!() const {
        return val == 0;
    }
    bool operator==(const Num& rhs) const {
        return val == rhs.val;
    }
    bool operator!=(const Num& rhs) const {
        return val != rhs.val;
    }
    bool operator>(const Num& rhs) const {
        return val > rhs.val;
    }
    bool operator>=(const Num& rhs) const {
        return val >= rhs.val;
    }
    bool operator<(const Num& rhs) const {
        return val < rhs.val;
    }
    bool operator<=(const Num& rhs) const {
        return val <= rhs.val;
    }
    bool operator&&(const Num& rhs) const {
        return val && rhs.val;
    }
    bool operator||(const Num& rhs) const {
        return val || rhs.val;
    }

};
#endif // MOD

动物装饰