--- title: "02-题目模板" created: 2025-11-28 tags: - 算法 --- > 📚 考场速抄的总模板:`#include ` 开头,typedef、方向数组、inf 一应俱全,往下是 30 级算法的模板代码,按注释分节。完整 .cpp 文件见本组 assets/template.cpp。无穷大取 0x3f3f3f3f 的原理见 [[04-刷题小贴士]]。 # template ```cpp #include using namespace std; #define endl '\n' //#define int long long 蓝桥杯用不了signed main using ll = long long; using ull = unsigned long long; using PII = pair; using Pll = pair; int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1}; const int inf = 0x3f3f3f3f; //----------------------- 30 级算法模板 ---------------------- // dfs | bfs //------------- dfs三种枚举 ------------- // 1、指数型枚举(子集枚举) void dfs_subset(int u,int n,vector& nums,vector& st){ if(u==n){ for(int i=0;i& nums){ sort(nums.begin(), nums.end()); //(需預先排序) do{ for(auto v:nums) cout<& nums,vector& way){ if(u+(n-start) bool isValid(int x, int y) { return x >= 0 && x < n && y >= 0 && y < m; // return x >= 1 && x <= n && y >= 1 && y <= m; // 1起 // return x >= 0 && x <= n+1 && y >= 0 && y <= m+1; //染色括一圈 } //-------------- 读图 -------------- template void readGrid(int n,int m,char (&g)[N][M]) { for(int i=0;i>s; for(int j=0;j void readGridWithSpace(int n,int (&g)[N][M]) { cin.ignore(); for(int i=0;i class DFSSolver { char g[N][M]; bool vis[N][M]; int n, m; void dfs(int x, int y) { // 终止条件处理 // ... for(int i=0;i<4;++i) { int nx=x+dx4[i],ny=y+dy4[i]; if(isValid(nx, ny) && !vis[nx][ny] && g[nx][ny] == '.') { vis[nx][ny] = true; dfs(nx,ny); vis[nx][ny] = false; // 根据需求决定是否回溯 } } } public: void solve() { cin>>n>>m; readGrid(n, m, g); memset(vis,0, sizeof vis); vis[0][0]=true; dfs(0,0); } }; //------------- BFS模板 ------------- template class BFSSolver { int g[N][M], d[N][M], pre[N][M]; const char dirs[4] = {'U','R','D','L'}; int n,m; void printPath(int x, int y) { if(x==0 && y==0) return; int p=pre[x][y]; int px=x-dx4[p],py=y-dy4[p]; printPath(px, py); cout<> n >> m; readGridWithSpace(n, g); memset(d, -1, sizeof d); queue q; q.push({0, 0}); d[0][0] = 0; while(!q.empty()) { auto cur = q.front(); q.pop(); int x=cur.first,y=cur.second; // if(x==targetx && y==targety){ // //dosomething // } for(int i = 0; i < 4; ++i) { int nx=x+dx4[i],ny=y+dy4[i]; if(isValid(nx, ny) && d[nx][ny] == -1 && g[nx][ny] == 0) { d[nx][ny] = d[x][y] + 1; pre[nx][ny] = i; q.push({nx, ny}); } } } printPath(n-1, m-1); } }; //---------- 洪水填充模板 ------------ template class FloodFill { char g[N][M]; bool vis[N][M]; int n, m, cnt; void dfs(int x, int y) { for(int i = 0; i < 8; ++i) { // 8方向填充 int nx = x + dx8[i], ny = y + dy8[i]; if(isValid(nx, ny) && !vis[nx][ny] && g[nx][ny] == '#') { vis[nx][ny] = true; dfs(nx, ny); } } } public: void solve() { cin >> n >> m; readGrid(n, m, g); cnt = 0; memset(vis, 0, sizeof vis); for(int i = 0; i < n; ++i) for(int j = 0; j < m; ++j) if(g[i][j] == '#' && !vis[i][j]) { vis[i][j] = true; dfs(i, j); ++cnt; } cout << "连通块数量: " << cnt << endl; } }; //---------------------- 八皇后问题 ------------------------- /* x,y (0,0) (0,1) (0,2) (0,3) (0,4) (1,0) (1,1) (1,2) (1,3) (1,4) (2,0) (2,1) (2,2) (2,3) (2,4) (3,0) (3,1) (3,2) (3,3) (3,4) (4,0) (4,1) (4,2) (4,3) (4,4) x+y (0) (1) (2) (3) (4) (1) (2) (3) (4) (5) (2) (3) (4) (5) (6) (3) (4) (5) (6) (7) (4) (5) (6) (7) (8) n-x+y (5) (6) (7) (8) (9) (4) (5) (6) (7) (8) (3) (4) (5) (6) (7) (2) (3) (4) (5) (6) (1) (2) (3) (4) (5) 正对角线 x+y 反对角线: n-x+y或者x-y+n */ class NQueens { static const int N = 20; char g[N][N]; bool col[N], dg[2*N], udg[2*N]; int n; void dfs(int u) { if(u == n) { for(int i = 0; i < n; ++i) puts(g[i]); puts(""); return; } for(int i = 0; i < n; ++i) { if(!col[i] && !dg[u+i] && !udg[n - u + i]) { g[u][i] = 'Q'; col[i] = dg[u+i] = udg[n - u + i] = true; dfs(u + 1); col[i] = dg[u+i] = udg[n - u + i] = false; g[u][i] = '.'; } } } public: void solve() { cin >> n; for(int i = 0; i < n; ++i) fill(g[i], g[i]+n, '.'); dfs(0); } }; /* DFSSolver<100, 100>().solve(); BFSSolver<100, 100>().solve(); FloodFill<100, 100>().solve(); NQueens().solve(); */ //------------------------ 100 级算法模板 --------------------- // dp // ----------------- 背包问题 ----------------- /* 01背包 逆序枚举体积 f[0]=0 每个物品选一次 完全背包 正序枚举体积 f[0]=0 物品无限选 多重背包 拆解后逆序枚举 f[0]=0 二进制拆分优化 二维费用 双逆序枚举 f[0][0]=0 双重限制条件 分组背包 组→体积→物品 f[0]=0 每组选一个 恰好装满(max) 常规顺序 f[0]=0, 其他-INF 确保状态合法性 恰好装满(count) 常规顺序 g[0]=1, 其他0 空包为唯一初始合法状态 至少装j(min) 常规顺序 f[0]=0, 其他INF 用max处理负数体积 */ /*---------- 01背包 ------------ 特点:每个物品选一次 状态转移:f[j] = max(f[j], f[j-v]+w) (逆序枚举) int f[MAXV]; memset(f, 0, sizeof f); for(int i=1; i<=n; i++) for(int j=m; j>=v[i]; j--) f[j] = max(f[j], f[j-v[i]]+w[i]); */ /*---------- 完全背包 ---------- 特点:物品无限选 状态转移:f[j] = max(f[j], f[j-v]+w) (正序枚举) int f[MAXV]; memset(f, 0, sizeof f); for(int i=1; i<=n; i++) for(int j=v[i]; j<=m; j++) f[j] = max(f[j], f[j-v[i]]+w[i]); */ /*---------- 二维费用背包 ---------- 限制条件:体积+重量双重限制 → 三维状态 int f[MAXV1][MAXV2]; memset(f, 0, sizeof f); for(int i=1; i<=n; i++) for(int j=m1; j>=v1[i]; j--) for(int k=m2; k>=v2[i]; k--) f[j][k] = max(f[j][k], f[j-v1[i]][k-v2[i]] + w[i]); */ /*---------- 分组背包 ---------- 每组选一个 → 三重循环顺序:物品组→体积→组内物品 int f[MAXV], v[N][N], w[N][N], s[N]; memset(f, 0, sizeof f); for(int i=1; i<=n; i++) // 枚举组 for(int j=m; j>=0; j--) // 枚举体积 for(int k=0; k= v[i][k]) f[j] = max(f[j], f[j-v[i][k]] + w[i][k]); */ /*----------方案数统计 ---------- 属性:count → 注意初始化和转移条件 int f[MAXV] = {0}, g[MAXV] = {0}; g[0] = 1; // 初始状态方案数为1 for(int i=1; i<=n; i++) for(int j=m; j>=v[i]; j--){ int maxv = max(f[j], f[j-v[i]] + w[i]); int cnt = 0; if(maxv == f[j]) cnt += g[j]; if(maxv == f[j-v[i]] + w[i]) cnt += g[j-v[i]]; f[j] = maxv; g[j] = cnt % MOD; } // 统计最大价值对应方案数 int res = 0, maxw = *max_element(f, f+m+1); for(int j=0; j<=m; j++) if(f[j] == maxw) res = (res + g[j]) % MOD; */ /*----------- 具体方案追踪 ----------- 逆序推导路径 → 通过状态转移反推选择 bool selected[N] = {false}; for(int i=n, j=m; i>=1; i--) if(j >= v[i] && f[i][j] == f[i-1][j-v[i]] + w[i]){ selected[i] = true; j -= v[i]; } */ //----------- 初始化策略 ----------- /*恰好装满(最大值) 初始:f[0]=0,其他为-INF → 确保状态合法性 int f[MAXV]; memset(f, 0xcf, sizeof f); // -INF f[0] = 0; for(int i=1; i<=n; i++) for(int j=m; j>=v[i]; j--) f[j] = max(f[j], f[j-v[i]] + w[i]); */ /*恰好装满(方案数) 初始:g[0]=1,其他为0 → 仅空包为合法初始状态 int g[MAXV] = {0}; g[0] = 1; for(int i=1; i<=n; i++) for(int j=m; j>=v[i]; j--) g[j] = (g[j] + g[j-v[i]]) % MOD; */ /*至少装j(最小值) 允许j-v为负数 → 用max(j-v,0)处理 int f[MAXV]; memset(f, 0x3f, sizeof f); // INF f[0] = 0; for(int i=1; i<=n; i++) for(int j=m; j>=0; j--) f[j] = min(f[j], f[max(j-v[i], 0)] + w[i]); */ // ----------------- 线性dp ----------------- /* 数字三角形 逆序递推,取max/min O(n2) 金字塔型路径规划 矩形单路径 二维DP,右/下转移 O(nm) 网格路径最优化 矩形双路径 三维状态压缩(步数k) O(n3) 资源重复利用问题 基本LIS 双重循环比较 O(n2) 序列特征分析 权值和LIS 在长度基础上累加权值 O(n2) 带权序列优化 双向LIS 正反两次DP求极值 O(n2) 山峰型序列分析 贪心优化LIS 维护单调队列+二分查找 O(nlogn) 大规模序列处理 最少划分上升子序列数 维护多个子序列的结尾值 O(nlogn) 任务调度问题 */ /*-------- 正三角形路径最大值 -------- 状态定义:f[i][j]表示从底边到(i,j)的最大路径和 转移方程:f[i][j] += max(左下值, 右下值) for(int i = n-1; i >= 1; i--){ // 从底向上递推 for(int j = 1; j <= i; j++){ f[i][j] += max(f[i+1][j], f[i+1][j+1]); } } cout << f[1][1]; // 顶点即为最大值 */ /*----------- 矩形路径处理 ----------- 特点:只能向右或向下移动 // 单路径最大值 for(int i=1; i<=n; i++){ for(int j=1; j<=m; j++){ f[i][j] = max(f[i-1][j], f[i][j-1]) + w[i][j]; } } cout << f[n][m]; // 双路径最大值(路径不重复) int f[N*2][N][N]; // 使用步数k压缩状态 for(int k=2; k<=n+m; k++){ // 总步数从2开始(起点(1,1)算1步) for(int i=1; i=1; i--){ f_down[i] = 1; for(int j=n; j>i; j--) if(a[j] < a[i]) f_down[i] = max(f_down[i], f_down[j]+1); } // 合并结果 int res = 0; for(int i=1; i<=n; i++) res = max(res, f_up[i] + f_down[i] - 1); */ /*---------- 贪心+二分优化LIS ---------- 时间复杂度:O(nlogn) int len = 0; // 当前最大长度 int q[N]; // 维护上升序列 for(int i=1; i<=n; i++){ int l=0, r=len; while(l < r){ // 找第一个>=a[i]的位置 int mid = (l+r) >> 1; if(q[mid] >= a[i]) r = mid; else l = mid + 1; } q[r] = a[i]; // 替换或追加 if(r == len) len++; } cout << len; */ /*---------- 最少划分上升子序列数 ---------- 贪心策略:维护多个上升子序列的结尾值 已有的单调栈只需要用一个栈顶元素表示 可以组成一个数组(单调) 每个数看能不能放在已有的栈里 且贪心的是栈顶离自己最近的 用二分找里自己最近的栈顶 若没有 就新开一个栈 最后看栈的数量 也就是数组的长度 int q[N],cnt; for(int i=1;i<=n;i++){ int l=0,r=cnt; while(l>1; if(q[mid]>=w[i]) r=mid; else l=mid+1; } if(q[r]= target 的位置 => lower_bound(nums.begin(), nums.end(), target) int findLeft(vector& nums,int target){ int l=0,r=nums.size(); while(l>1; if(nums[mid]>=target) r=mid; else l=mid+1; } return r; } //-----| ----- 最后一个 <= target 的元素位置 => upper_bound(nums.begin(), nums.end(), target) - 1 int findRight(vector& nums,int target){ int l=0,r=nums.size(); while(l>1; if(nums[mid]<=target) l=mid; else r=mid-1; } return r; } //------------------------ 1e5 级算法模板 ----------------------- // 贪心 | 排序 | set、map、heap | 二分 ↑ //------------- 排序与比较 ------------- // 单关键字 /* sort(v.begin(), v.end()); sort(v.begin(), v.end(), greater()); priority_queue maxheap; priority_queue,greater> minheap; */ // 多关键字 struct Task { int priority; // 优先级(值越大越重要) int duration; // 耗时(值越小越好) string name; // 方法1:重载运算符(固定排序规则) // 默认排序规则:先按优先级降序,再按耗时升序 bool operator<(const Task& rhs) const { if(priority != rhs.priority) return priority > rhs.priority; // 降序 return duration < rhs.duration; // 升序 } }; // 方法2:自定义比较器(灵活调整规则) struct TaskComparator { bool operator()(const Task& a, const Task& b) { if(a.priority != b.priority) return a.priority < b.priority; // 升序 return a.duration > b.duration; // 降序 } }; /* 使用示例: vector tasks; // 使用重载运算符排序(默认规则) sort(tasks.begin(), tasks.end()); // 使用自定义比较器排序(不同规则) sort(tasks.begin(), tasks.end(), TaskComparator()); // 优先队列使用说明 priority_queue pq1; // 使用operator<,大顶堆(高优先级在前) priority_queue, TaskComparator> pq2; // 使用自定义规则 */ /* .push(); .top(); .pop(); .size(); .empty(); while(!pq.empty()){ cout< s; s.insert(3); if(s.find(3) != s.end()) s.erase(3); s.size(); s.empty(); for(auto c:s) cout<> descending_set = {5, 2, 7, 1}; // {7,5,2,1} struct Student { string name; int score; // 重载 < 运算符(默认用于set排序) bool operator<(const Student& rhs) const { return score > rhs.score; // 分数降序排列 } }; set student_set; // 自动使用operator<排序 student_set.insert({"Alice", 90}); student_set.insert({"Bob", 85}); // 快速去重并获取有序结果: vector nums = {5,3,5,1,3,7}; set unique_sorted(nums.begin(), nums.end()); vector result(unique_sorted.begin(), unique_sorted.end()); 相当于: sort(nums.begin(),nums.end()); nums.erase(unique(nums.begin(),nums.end()),nums.end()); //sort + unique + erase 的组合在性能上更优 // 范围查询: set data = {10,20,30,40,50}; // 查找 [25,45) 范围内的元素 auto low = data.lower_bound(25); // 第一个 >=25 的元素(30) auto high = data.upper_bound(45); // 第一个 >45 的元素(50) for (auto it = low; it != high; ++it) { cout << *it << " "; // 30 40 } unordered_map: //基本用法: unordered_map age_map; // 插入元素 age_map["Alice"] = 25; age_map.insert({"Bob", 30}); // 访问元素(需注意键是否存在) if(age_map.count("Alice")) { cout << age_map.at("Alice"); // cout<< age_map["Alice"]; } // 遍历所有键值对 for(auto& [name, age] : age_map) { //c++17 比赛应该用不了 cout << name << ": " << age << endl; } for(auto x : age_map){ cout << x.first << ": " << x.second << endl; } */ //------------------------ 1e6 级算法模板 ----------------------- // 双指针 | 并查集 | hash | KMP × | bitset //------------- 双指针模板 ---------------- // 1:同向双指针(滑动窗口) int slidingWindow(vector& nums, int k) { unordered_map freq; int left = 0, max_len = 0; for (int right = 0; right < nums.size(); ++right) { freq[nums[right]]++; while (freq.size() > k) { if (--freq[nums[left]] == 0) freq.erase(nums[left]); left++; } max_len = max(max_len, right - left + 1); } return max_len; } // 2:对撞指针(两数之和) bool twoSum(vector& nums, int target) { sort(nums.begin(), nums.end()); int left = 0, right = nums.size()-1; while (left < right) { int sum = nums[left] + nums[right]; if (sum == target) return true; if (sum < target) left++; else right--; } return false; } // 3:序列匹配(判断子序列) bool isSubsequence(string s, string t) { int i = 0; for (int j = 0; j < t.size() && i < s.size(); j++) { if (s[i] == t[j]) i++; } return i == s.size(); } //---------------- 并查集 ---------------- struct DSU{ vector parent; DSU(int n){ parent.resize(n+1); 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]; } void unite(int x, int y) { int fx = find(x); int fy = find(y); if(fx!=fy){ parent[fx]=fy; } } bool connected(int x, int y) { return find(x) == find(y); } }; /* 使用示例: DSU dsu(n); dsu.unite(a, b); if (dsu.connected(x, y)) {...} */ //---------------- 字符串哈希 --------------- const int N6 = 1e6+10; const int P = 131; class StringHash{ private: ull h[N6]; ull p[N6]; int n; public: void init(const char* str ,int length){ n=length; p[0]=1; for(int i=1;i<=n;i++){ h[i]=h[i-1]*P+str[i]; p[i]=p[i-1]*P; } } ull get_hash(int l,int r) const { if(l>r || l<1 || r>n) return 0; return h[r]-h[l-1]*p[r-l+1]; } }; /* 使用示例: char s[N6];cin>>s+1; int len=strlen(s+1); StringHash hasher; hasher.init(s,len); int l1,r1,l2,r2; cin>>l1>>r1>>l2>>r2; bool match(hasher.get_hash(l1,r1) == hasher.get_hash(l2,r2)); cout<<(match?"Yes":"No")< bs; // 初始化N位全0 * bitset bs(val); // 用整型val初始化(十进制/十六进制) * bitset bs(str); // 用"0101"格式字符串初始化 */ // 示例:8位bitset操作 void bitset_demo() { const int N = 8; bitset bs1(0b11011010); // 二进制初始化 bitset bs2(218); // 十进制初始化 bitset bs3("11011010"); // 字符串初始化 // 位操作 bs1.set(0); // 第0位置1 (从右数) bs1.reset(1); // 第1位置0 bs1.flip(2); // 第2位取反 bool bit = bs1.test(3); // 检查第3位 // 统计与转换 int cnt = bs1.count(); // 1的个数 string s = bs1.to_string(); // 转"01101011"格式 unsigned long num = bs1.to_ulong(); // 转数值 // 运算符重载 auto bs_and = bs1 & bs2; // 按位与 auto bs_or = bs1 | bs2; // 按位或 auto bs_xor = bs1 ^ bs2; // 按位异或 bs1 <<= 2; // 左移2位 bs1 >>= 1; // 右移1位 // 全量操作 bs1.set(); // 所有位置1 bs1.reset(); // 所有位置0 bs1.flip(); // 所有位取反 } //------------------------ 1e7 级算法模板 ----------------------- // 双指针 ↑ | kmp ↑ | 筛法 //---------------- 线性筛 --------------- const int N7=1e7+10; int primes[N7]; bool st_isPrime[N7]; int primes_cnt=0; void get_primes(int n){ // 获得n以内的所有素数 for(int i=2;i<=n;i++){ if(!st_isPrime[i]) primes[primes_cnt++]=i; for(int j=0;primes[j]<=n/i;j++){ st_isPrime[primes[j]*i]=true; if(i%primes[j]==0) break; } } } //------------------------ 1e9 级算法模板 ----------------------- // 判断质数 | 约数 bool is_prime(int n){ // 判断n是不是素数 if(n<2) return false; for(int i=2;i<=n/i;i++){ if(n%i==0) return false; } return true; } set divisors; void get_divisors(int n){ //获得n的所有约数 divisors.clear(); for(int i=1;i<=n/i;i++){ if(n%i==0){ divisors.insert(i); divisors.insert(n/i); } } } unordered_map weight; int calc_divisors_cnt(int n){ //计算n的约数个数 相当于divisors.size() 但适用于n非常大 甚至无法存的情况 weight.clear(); int res=1; for(int i=2;i<=n/i;i++){ while(n%i==0){ weight[i]++; n/=i; } } if(n>1) weight[n]++; for(auto x:weight) res=res*(x.second+1); return res; } //------------------------ 1e18 级算法模板 ----------------------- // gcd | lcm | 快速幂 ll gcd(ll a, ll b) { return b ? gcd(b, a%b) : a; } ll lcm(ll a, ll b) { return a / gcd(a,b) * b; } ll gcd(ll a,ll b, ll c){ return gcd(gcd(a,b),c); } ll lcm(ll a,ll b,ll c){ return lcm(lcm(a,b),c); } ll qmi(ll a,ll k,ll p){ // 求 a^k mod p ll res=1%p; while(k){ if(k&1) res=res*a%p; k>>=1; a=a*a%p; } return res; } //------------------------ 1e18往上 级算法模板 ----------------------- // int_128 | 高精度模拟 // int->2e10 ll->9e19 int_128->1e38 bool big_cmp(const vector& A,const vector& B){ if(A.size()!=B.size()) return A.size()>B.size(); for(int i=A.size()-1;i>=0;i--){ if(A[i]!=B[i]) return A[i]>B[i]; } return true; } vector big_add(vector A,vector B){ vector C; int t=0; for(int i=0;iB if(big_cmp(A,B)) auto C=big_sub(A,B) else auto C=big_sub(B,A) 补充负号 vector big_sub(vector A,vector B){ vector C; int t=0; for(int i=0;i1 && C.back()==0) C.pop_back(); return C; } vector big_mul(vector A,int b){ vector C; int t=0; for(int i=0;i1 && C.back()==0) C.pop_back(); return C; } vector big_div(vector A,int b,int& r){ vector C; r=0; for(int i=A.size()-1;i>=0;i--){ r=r*10+A[i]; C.push_back(r/b); r%=b; } reverse(C.begin(),C.end()); while(C.size()>1 && C.back()==0) C.pop_back(); return C; } vector big_mul_big(const vector& A, const vector& B) { vector C(A.size()+B.size(), 0); for(int i = 0; i < A.size(); i++) for(int j = 0; j < B.size(); j++) C[i+j] += A[i] * B[j]; for(int i = 0, t = 0; i < C.size(); i++) { t += C[i]; C[i] = t % 10; t /= 10; } while(C.size() > 1 && C.back() == 0) C.pop_back(); return C; } vector str2big(const string& s){ vector res; for(int i=s.size()-1;i>=0;i--){ if(isdigit(s[i])) res.push_back(s[i]-'0'); else if(s[i]=='-' && i==0) break; } while(res.size()>1 && res.back()==0) res.pop_back(); return res; } string big2str(const vector& A){ string res; for(int i=A.size()-1;i>=0;i--) res+=to_string(A[i]); return res.empty()?"0":res; } //--------------------------- other -------------------------- //----------日期问题 --------- int days[13]={0,31,28,31,30,31,30,31,31,30,31,30,31}; bool is_leap(int y){ return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); } int get_days(int y,int m){ return days[m]+(m==2 && is_leap(y)); } void next_day(int &y,int &m,int &d){ d++; if(d>get_days(y,m)){ d=1; m++; if(m>12){ m=1; y++; } } } bool check_date(int y,int m,int d){ if(m<1 || m>12) return false; if(d<1 || d>get_days(y,m)) return false; return true; } /* while((curYear < targetYear) || (curYear == targetYear && curMonth < targetMonth) || (curYear == targetYear && curMonth == targetMonth && curDay < targetDay)){ dosomething(); next_day(curYear,curMonth,curDay); } char buffer[10]; sprintf(buffer,"%04d%02d%02d",curYear,curMonth,curDay); string s(buffer); */ //---------- 前缀和与差分 ----------- /* 一维前缀和: s[0] = 0; for(int i=1; i<=n; i++) s[i] = s[i-1] + a[i]; 查询 [l,r]:s[r] - s[l-1] 二维前缀和: s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]; 查询 (x1,y1)-(x2,y2):s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1] 一维差分: void insert(int l, int r, int c) { b[l] += c; b[r+1] -= c; } 初始化:for i insert(i,i,a[i]) 区间加:insert(l,r,c) 求原数组:b[i] += b[i-1] 二维差分: void insert(int x1,int y1,int x2,int y2,int c) { b[x1][y1] += c; b[x2+1][y1] -= c; b[x1][y2+1] -= c; b[x2+1][y2+1] += c; } 初始化:for i,j insert(i,j,i,j,a[i][j]) 区域加:insert(x1,y1,x2,y2,c) 求原数组:b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1] */ //---------------- 单调栈 ------------------ //-------- 最近问题 -------- /* 左侧最近的小于当前元素 遍历方向:左 →右 单调性:递增栈(栈顶最小) 操作:若当前元素≤栈顶,弹出栈顶,直到栈顶更小,此时栈顶为结果,入栈当前元素 int stk[N], tt = 0; for (int i = 0; i < n; i++) { while (tt && a[i] <= stk[tt]) tt--; ans[i] = tt ? stk[tt] : -1; // 记录结果 stk[++tt] = a[i]; } 左侧最近的大于当前元素 遍历方向:左→右 单调性:递减栈(栈顶最大) 操作:类似上述,条件改为a[i] >= stk[tt] int stk[N], tt = 0; for (int i = 0; i < n; i++) { while (tt && a[i] >= stk[tt]) tt--; ans[i] = tt ? stk[tt] : -1; stk[++tt] = a[i]; } 右侧最近的小于当前元素 遍历方向:右→左 单调性:递增栈 for (int i = n-1; i >=0; i--) { while (tt && a[i] <= stk[tt]) tt--; ans[i] = tt ? stk[tt] : -1; stk[++tt] = a[i]; } 右侧最近的大于当前元素 遍历方向:右→左 单调性:递减栈 for (int i = n-1; i >=0; i--) { while (tt && a[i] >= stk[tt]) tt--; ans[i] = tt ? stk[tt] : -1; stk[++tt] = a[i]; } */ //------ 最远问题(结合二分) ------ /* 左侧最远的大于当前元素 构造一个单调递增的序列 若这个新加的数能保持递增趋势 说明不存在比它更大的数 没必要去找了 直接加进去 供后面的数去找 如果不能保持递增趋势 就说明左边有一个比它更大的数 在这个已有的单调序列中二分找到 vector stk; //需要注意的一点是 stk存的是下标 for(int i=1;i<=n;i++){ //a[i]>a[stk.back()] 还是 a[i]>=a[stk.back()] //加不加等号看题意 //找严格小于它的数的话 这里加等号 意味着找到它自己 //找前面小于等于它的数的话 这里不加等号 if(stk.empty() || a[i]>=a[stk.back()]){//发现该元素比栈顶元素还大(保持单调递增) stk.push_back(i);//直接入栈 ans[i]=i;//记录答案 可能求长度等等 } else{//发现该元素不是最大 那么原单调序列里肯定存在一些比他大的数 二分找到最远的那个 int l=1,r=stk.size()-1; while(l>1; if(a[stk[mid]]>a[i]) r=mid; else l=mid+1; } ans[i]=stk[r]; } } 左边最远的小于当前元素 找最小的数 如果本身已经是最小了 就没必要找 直接加入单调栈保持单调性 如果自身不是最小 也不做什么踢出操作 直接在单调栈里找答案就行 只不过它也没必要加进去 vector stk; for(int i=1;i<=n;i++){ if(stk.empty() || a[i]>1; if(a[stk[mid]]=1;i--){//只需改循环 if(stk.empty() || a[i]>a[stk.back()]){ stk.push_back(i); ans[i]=i; } else{ int l=0,r=stk.size()-1; while(l>1; if(a[stk[mid]]>a[i]) r=mid; else l=mid+1; } ans[i]=stk[r]; } } 右边 最远的 小于它的数 n-1 最小 若该数为最小 直接加入 否则 二分找 for(int i=n;i>=1;i--){ if(stk.empty() || a[i]>1; if(a[stk[mid]] q; vector res; for (int i = 0; i < n; i++) { if (!q.empty() && i - q.front() >= k) q.pop_front(); while (!q.empty() && a[i] <= a[q.back()]) q.pop_back(); //求最小 递增区间 若出现向下 出队 q.push_back(i); if (i >= k-1) res.push_back(a[q.front()]); //当前区间的最大值为队头 } 窗口最大值 while (!q.empty() && a[i] >= a[q.back()]) q.pop_back(); */ //-------------- 区间合并 --------------- /* 多次要对一个大区间的某些段做标记 可以暴力对每一段做标记 最后枚举整个区间 看是否有没标记过的 很多时候也可以用差分 某段处理后就在该段++ 不过我们要的结果不在乎具体是多少 只是有值就1 没值就0 然后对于暴力对每段做标记和差分的话 都会存在一个问题 有很多地方做了重复操作 这个时候就可以使用区间合并进行优化 把能合并的区间都合并起来 再对他们去做操作 就可以省去一些重复功 对于合并操作 得分析一个问题 题目中诸如:13 45 这样的能不能合并 如果能 模版处就改成ed+1 PII; PII range[N]; int cnt; range[cnt++]={l,r}; sort(range,range+cnt); vector merged; // 存储合并后的区间 if (cnt == 0) { // 无区间处理 } else { int st = range[0].first, ed = range[0].second; for (int i = 1; i < cnt; i++) { // 判断是否可合并(允许相邻区间合并) if (range[i].first <= ed + 1) { ed = max(ed, range[i].second); } else { merged.push_back({st, ed}); st = range[i].first; ed = range[i].second; } } merged.push_back({st, ed}); // 加入最后一个区间 } */ //----------------- 数学 ---------------- // 转double -> a*1.0/b // 除法尽量变乘法 // 等差数列 // a_n = a_1 + (n-1)d // s_n = (n/2)(a_1+a_n) = (n/2)(2a_1 + (n-1)d) = na_1 + n(n-1)/2d // 等比数列 // a_n = a_1 * r^(n-1) // s_n = a_1 * (1 - r^n) / 1-r (r!=1) // 计算几何 // 点斜式:y-y1=k(x-x1) // 截距式:y=kx+b // double k = (double) (y2-y1) / (x2-x1); // double b = (double) y1-k*x1 // double x = (double) (b2-b1) / (k1-k2); // round() 四舍五入 // floor() 向下取整 // ceil() 向上取整 // pow() 指数幂 ldexp() 浮点数幂运算 // sqrt() 平方根 // log2() log10 对数 //----------------- 位运算 ---------------- // & 同真为真 其余为假 x&1相当于看x为多少 // if(x&1) 奇数 if((X&1)==0) 偶数 // | 同假为假 其余为真 // ^ 相同为0,不同为1 x^y^y等于什么都没做 若x==y 则x^y=0 // >> x>>=1 相当于 x/=2 x>>=n 相当于 x/=2^n // << x<