文章目录
算法题知识点总结与代码模板
基于湖南省赛 CSG 题库整理,共 149 道题目
所有模板均为 C++ 竞赛常用写法
目录
一、图论
1.1 Dijkstra 最短路(堆优化)
适用场景: 单源最短路,非负权边
const int INF = 0x3f3f3f3f;
vector<pair<int, int>> g[N]; // to, weight
int dist[N];
void dijkstra(int s, int n) {
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
memset(dist, 0x3f, sizeof(dist[0]) * (n + 1));
dist[s] = 0;
pq.push({0, s});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d > dist[u]) continue;
for (auto [v, w] : g[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
}
思路要点:
- 每次选距离最小的点扩展(贪心)
- 用优先队列(小根堆)优化选点
- 出队时若距离已更新则跳过(懒惰删除)
- 时间复杂度 O((V+E) log V)
相关题目: CSG 1052, 1109, 1116, 1137, 1139, 1190, 1193
1.2 Kruskal 最小生成树
适用场景: 稀疏图求MST
struct Edge { int u, v, w; };
vector<Edge> edges;
// 并查集
int fa[N];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int kruskal(int n) {
sort(edges.begin(), edges.end(), [](Edge& a, Edge& b) { return a.w < b.w; });
for (int i = 1; i <= n; i++) fa[i] = i;
int res = 0, cnt = 0;
for (auto& e : edges) {
int u = find(e.u), v = find(e.v);
if (u != v) {
fa[u] = v;
res += e.w;
if (++cnt == n - 1) break;
}
}
return res; // cnt < n-1 则不连通
}
思路要点:
- 边按权值从小到大排序
- 依次加边,用并查集判环
- 选满 n-1 条边结束
- 时间复杂度 O(E log E)
相关题目: CSG 1099, 1100
1.3 LCA 最近公共祖先(倍增法)
适用场景: 树上两点最近公共祖先、路径查询
vector<int> g[N];
int depth[N], up[20][N]; // up[k][u] 表示u的2^k级祖先
void dfs(int u, int fa) {
up[0][u] = fa;
for (int i = 1; i < 20; i++)
up[i][u] = up[i-1][up[i-1][u]];
for (int v : g[u]) {
if (v == fa) continue;
depth[v] = depth[u] + 1;
dfs(v, u);
}
}
int lca(int u, int v) {
if (depth[u] < depth[v]) swap(u, v);
// 跳到同一深度
int diff = depth[u] - depth[v];
for (int i = 0; i < 20; i++)
if (diff >> i & 1) u = up[i][u];
if (u == v) return u;
for (int i = 19; i >= 0; i--)
if (up[i][u] != up[i][v])
u = up[i][u], v = up[i][v];
return up[0][u];
}
思路要点:
- DFS预处理深度和倍增表
- 先将两点跳到同一深度
- 从大到小枚举二进制位,同时上跳
- 时间:预处理 O(n log n),查询 O(log n)
相关题目: CSG 1077, 1088, 1129, 1142, 1217, 1322
1.4 Dinic 最大流
适用场景: 最大流、最小割、二分图匹配
struct Edge { int to, rev; long long cap; };
vector<Edge> g[N];
int level[N], iter[N];
void add_edge(int from, int to, long long cap) {
g[from].push_back({to, (int)g[to].size(), cap});
g[to].push_back({from, (int)g[from].size()-1, 0});
}
void bfs(int s) {
memset(level, -1, sizeof level);
queue<int> q; level[s] = 0; q.push(s);
while (!q.empty()) {
int u = q.front(); q.pop();
for (auto& e : g[u]) {
if (e.cap > 0 && level[e.to] < 0) {
level[e.to] = level[u] + 1;
q.push(e.to);
}
}
}
}
long long dfs(int u, int t, long long f) {
if (u == t) return f;
for (int& i = iter[u]; i < g[u].size(); i++) {
Edge& e = g[u][i];
if (e.cap > 0 && level[u] < level[e.to]) {
long long d = dfs(e.to, t, min(f, e.cap));
if (d > 0) {
e.cap -= d;
g[e.to][e.rev].cap += d;
return d;
}
}
}
return 0;
}
long long max_flow(int s, int t) {
long long flow = 0;
while (true) {
bfs(s);
if (level[t] < 0) return flow;
memset(iter, 0, sizeof iter);
long long f;
while ((f = dfs(s, t, 1e18)) > 0) flow += f;
}
}
思路要点:
- BFS 建分层图(level数组)
- DFS 找阻塞流(当前弧优化 iter)
- 重复 BFS+DFS 直到无法增广
- 时间复杂度 O(E * V²),单位容量网络 O(E√V)
相关题目: CSG 1062, 1189
📈 二、动态规划
📈 2.1 最长上升子序列(O(n log n))
适用场景: LIS长度、LIS计数相关
int lis(vector<int>& a) {
vector<int> tails;
for (int x : a) {
auto it = lower_bound(tails.begin(), tails.end(), x);
if (it == tails.end()) tails.push_back(x);
else *it = x;
}
return tails.size();
}
思路要点:
tails[i]表示长度为i+1的LIS末尾最小元素- 每个数二分找到替换位置
- 最终
tails长度就是 LIS 长度 - 注意:求最长不下降用
upper_bound
相关题目:
- CSG 1138
📦 2.2 01 背包
适用场景: 每件物品选或不选
// 01背包:n件物品,体积V,求最大价值
int dp[N]; // dp[j] 表示体积j的最大价值
void knapsack01(int n, int V, int w[], int v[]) {
memset(dp, 0, sizeof dp);
for (int i = 0; i < n; i++)
for (int j = V; j >= w[i]; j--) // 倒序!
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
// 完全背包:每件可选无限次(正序遍历)
void knapsack_full(int n, int V, int w[], int v[]) {
memset(dp, 0, sizeof dp);
for (int i = 0; i < n; i++)
for (int j = w[i]; j <= V; j++) // 正序!
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
思路要点:
- 01背包倒序:保证每件只选一次
- 完全背包正序:可重复选
- 滚动数组优化:体积大但价值小时优化空间
- 常见变种:恰好装满、方案数、多重背包(二进制拆分)
相关题目:
- CSG 1043
- CSG 1044
- CSG 1163
📏 2.3 区间 DP
适用场景: 石子合并、回文串、矩阵链乘
// 石子合并:dp[l][r] 合并区间[l,r]的最小代价
int dp[N][N], sum[N];
int interval_dp(int a[], int n) {
for (int i = 1; i <= n; i++) sum[i] = sum[i-1] + a[i];
memset(dp, 0x3f, sizeof dp);
for (int i = 1; i <= n; i++) dp[i][i] = 0;
for (int len = 2; len <= n; len++) // 区间长度
for (int l = 1; l + len - 1 <= n; l++) { // 左端点
int r = l + len - 1;
for (int k = l; k < r; k++) // 枚举分割点
dp[l][r] = min(dp[l][r], dp[l][k] + dp[k+1][r] + sum[r] - sum[l-1]);
}
return dp[1][n];
}
思路要点:
- 外层枚举区间长度,内层枚举左端点
- 枚举分割点 k,合并左右子区间
- 时间复杂度 O(n³)
- 四边形不等式可优化到 O(n²)
相关题目:
- CSG 1069, 1144
📊 2.4 数位 DP(记忆化搜索)
适用场景: 统计 [1,n] 中满足某数位条件的数的个数
long long dp[20][10][2]; // 位置、状态、紧约束
int digit[20];
long long dfs(int pos, int state, bool tight, bool lead, vector<int>& d) {
if (pos == -1) return check(state); // 检查是否合法
if (!tight && !lead && dp[pos][state] != -1) return dp[pos][state];
int upper = tight ? d[pos] : 9;
long long res = 0;
for (int i = 0; i <= upper; i++) {
res += dfs(pos-1, new_state(state, i),
tight && i == upper, lead && i == 0, d);
}
if (!tight && !lead) dp[pos][state] = res;
return res;
}
long long solve(long long x) {
int len = 0;
while (x) { digit[len++] = x % 10; x /= 10; }
memset(dp, -1, sizeof dp);
return dfs(len-1, 0, true, true, digit);
}
思路要点:
- 按位处理,记忆化搜索实现
- 关键参数:位置、状态、紧约束、前导零
- 紧约束(tight):前面都贴着上限,当前位不能超
- 前导零(lead):前导零不算入数位统计
相关题目: CSG 1048, 1072
📦 2.5 状态压缩 DP
适用场景: n ≤ 20 的集合类问题
// TSP:dp[mask][i] 经过mask集合,当前在i点
int dp[1<<20][20];
int tsp(int n, int dist[][20]) {
memset(dp, 0x3f, sizeof dp);
dp[1][0] = 0; // 从0出发
for (int mask = 1; mask < (1<<n); mask++) {
for (int i = 0; i < n; i++) {
if (!(mask >> i & 1)) continue;
for (int j = 0; j < n; j++) {
if (mask >> j & 1) continue;
dp[mask | (1<<j)][j] = min(dp[mask | (1<<j)][j],
dp[mask][i] + dist[i][j]);
}
}
}
return dp[(1<<n)-1][n-1];
}
思路要点:
- 用二进制位表示集合状态
- 枚举 mask,枚举当前点,转移到下一个点
- 时间复杂度 O(n² * 2ⁿ)
- n 超过 20 基本不可行
相关题目:
- CSG 1087, 1194, 1195
📜 三、字符串
3.1 KMP / 前缀函数
适用场景: 模式匹配、周期串、前缀后缀相关
// 前缀函数:pi[i] = s[0..i]最长相等真前缀后缀长度
vector<int> get_pi(string& s) {
int n = s.size();
vector<int> pi(n, 0);
for (int i = 1; i < n; i++) {
int j = pi[i-1];
while (j > 0 && s[i] != s[j]) j = pi[j-1];
if (s[i] == s[j]) j++;
pi[i] = j;
}
return pi;
}
// KMP匹配:返回t中所有p出现的位置
vector<int> kmp(string& t, string& p) {
vector<int> pi = get_pi(p);
vector<int> res;
int j = 0;
for (int i = 0; i < t.size(); i++) {
while (j > 0 && t[i] != p[j]) j = pi[j-1];
if (t[i] == p[j]) j++;
if (j == p.size()) {
res.push_back(i - j + 1);
j = pi[j-1];
}
}
return res;
}
思路要点:
- 前缀函数利用已匹配信息,避免暴力回溯
- 核心:失配时跳到 pi[j-1] 继续比较
- 时间复杂度 O(n + m)
相关题目: CSG 1199, 1215, 1328
3.2 📚 Trie 字典树
适用场景: 前缀查询、字符串统计、异或最值
struct Trie {
int ch[N][26], cnt[N], idx;
Trie() { idx = 1; }
void insert(string& s) {
int u = 0;
for (char c : s) {
int v = c - 'a';
if (!ch[u][v]) ch[u][v] = idx++;
u = ch[u][v];
}
cnt[u]++;
}
int query(string& s) {
int u = 0;
for (char c : s) {
int v = c - 'a';
if (!ch[u][v]) return 0;
u = ch[u][v];
}
return cnt[u];
}
};
思路要点:
- 每个节点代表一个前缀
- 根节点为空串,路径上的字符组成字符串
- 插入、查询都是 O(len)
- 01Trie 可求最大异或和
相关题目: CSG 1061, 1166, 1055
📦 四、数据结构
4.1 线段树(懒标记)
适用场景: 区间修改、区间查询(和、最值等)
struct SegTree {
long long sum[N << 2], lazy[N << 2];
void push_up(int p) {
sum[p] = sum[p<<1] + sum[p<<1|1];
}
void push_down(int p, int l, int r) {
if (lazy[p]) {
int mid = (l + r) >> 1;
sum[p<<1] += lazy[p] * (mid - l + 1);
sum[p<<1|1] += lazy[p] * (r - mid);
lazy[p<<1] += lazy[p];
lazy[p<<1|1] += lazy[p];
lazy[p] = 0;
}
}
void build(int p, int l, int r, int a[]) {
if (l == r) { sum[p] = a[l]; return; }
int mid = (l + r) >> 1;
build(p<<1, l, mid, a);
build(p<<1|1, mid+1, r, a);
push_up(p);
}
void update(int p, int l, int r, int ul, int ur, int val) {
if (ul <= l && r <= ur) {
sum[p] += val * (r - l + 1);
lazy[p] += val;
return;
}
push_down(p, l, r);
int mid = (l + r) >> 1;
if (ul <= mid) update(p<<1, l, mid, ul, ur, val);
if (ur > mid) update(p<<1|1, mid+1, r, ul, ur, val);
push_up(p);
}
long long query(int p, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return sum[p];
push_down(p, l, r);
int mid = (l + r) >> 1;
long long res = 0;
if (ql <= mid) res += query(p<<1, l, mid, ql, qr);
if (qr > mid) res += query(p<<1|1, mid+1, r, ql, qr);
return res;
}
};
思路要点:
- 每个节点代表一个区间,二叉树结构
- 懒标记:延迟下传修改,整块更新时打标记
- 查询/更新到子节点前必须 push_down
- 时间复杂度 O(log n) 单次操作
相关题目: CSG 1045, 1086, 1191, 1217
4.2 树状数组(Fenwick Tree)
适用场景: 单点修改、前缀查询(和、最值等)
struct Fenwick {
long long tree[N];
int n;
void init(int size) { n = size; memset(tree, 0, sizeof tree); }
void add(int i, long long x) {
for (; i <= n; i += i & -i)
tree[i] += x;
}
long long query(int i) {
long long res = 0;
for (; i > 0; i -= i & -i)
res += tree[i];
return res;
}
long long range_query(int l, int r) {
return query(r) - query(l - 1);
}
};
思路要点:
- lowbit = i & -i,取最低位的1
- 加操作:向上跳,累加
- 查询操作:向下跳,求和
- 空间小、常数小,功能弱于线段树
- 时间复杂度 O(log n)
相关题目: CSG 1086, 1094
4.3 并查集(DSU)
适用场景: 连通性、合并集合、Kruskal
struct DSU {
int fa[N], sz[N];
void init(int n) {
for (int i = 1; i <= n; i++) fa[i] = i, sz[i] = 1;
}
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]); // 路径压缩
}
bool unite(int x, int y) {
x = find(x), y = find(y);
if (x == y) return false;
if (sz[x] < sz[y]) swap(x, y); // 按秩合并
fa[y] = x;
sz[x] += sz[y];
return true;
}
bool same(int x, int y) {
return find(x) == find(y);
}
};
思路要点:
- 路径压缩:查询时顺便把父节点指向根
- 按秩合并:小集合挂到大集合上
- 近似常数时间复杂度
- 可扩展:带权并查集、可撤销并查集
相关题目: CSG 1099(Kruskal内部), 1079
4.4 单调栈
适用场景: 找左右第一个更大/更小元素、柱状图最大矩形
// 找每个元素右边第一个比它大的元素下标
vector<int> next_greater(vector<int>& a) {
int n = a.size();
vector<int> res(n, -1);
stack<int> st;
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && a[st.top()] <= a[i]) st.pop();
if (!st.empty()) res[i] = st.top();
st.push(i);
}
return res;
}
// 柱状图最大矩形面积
long long largest_rectangle(vector<int>& h) {
int n = h.size();
vector<int> left(n), right(n);
stack<int> st;
for (int i = 0; i < n; i++) {
while (!st.empty() && h[st.top()] >= h[i]) st.pop();
left[i] = st.empty() ? -1 : st.top();
st.push(i);
}
while (!st.empty()) st.pop();
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && h[st.top()] >= h[i]) st.pop();
right[i] = st.empty() ? n : st.top();
st.push(i);
}
long long ans = 0;
for (int i = 0; i < n; i++)
ans = max(ans, 1LL * h[i] * (right[i] - left[i] - 1));
return ans;
}
思路要点:
- 维护栈内元素单调性(递增或递减)
- 每个元素入栈出栈各一次,O(n)
- 核心:弹出条件决定了"第一个不满足"的位置
相关题目: CSG 1069, 1094
📐 五、数学与数论
5.1 快速幂 / 逆元 ⚡
适用场景: 大指数取模、组合数预处理
long long pow_mod(long long a, long long b, long long mod) {
long long res = 1;
a %= mod;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
// 费马小定理求逆元(mod为质数)
long long inv(long long a, long long mod) {
return pow_mod(a, mod - 2, mod);
}
思路要点:
- 二进制拆分指数,逐位平方
- 费马小定理:a^(p-1) ≡ 1 mod p → a^(p-2) ≡ a^(-1)
- 时间复杂度 O(log b)
5.2 组合数预处理
适用场景: 频繁查询组合数 C(n,k) mod p
const int MOD = 1e9 + 7;
long long fact[N], inv_fact[N];
void init_combo(int n) {
fact[0] = 1;
for (int i = 1; i <= n; i++)
fact[i] = fact[i-1] * i % MOD;
inv_fact[n] = pow_mod(fact[n], MOD - 2, MOD);
for (int i = n - 1; i >= 0; i--)
inv_fact[i] = inv_fact[i+1] * (i+1) % MOD;
}
long long C(int n, int k) {
if (k < 0 || k > n) return 0;
return fact[n] * inv_fact[k] % MOD * inv_fact[n-k] % MOD;
}
思路要点:
- 预处理阶乘和阶乘逆元
- C(n,k) = n! / (k! * (n-k)!)
- 单次查询 O(1)
- n 很大 mod 很小时用卢卡斯定理
相关题目: CSG 1070, 1054, 1158
5.3 矩阵快速幂
适用场景: 线性递推加速、图上路径计数
const int MOD = 1e9 + 7;
struct Matrix {
long long m[105][105];
int n;
Matrix(int size) : n(size) { memset(m, 0, sizeof m); }
Matrix operator*(const Matrix& b) const {
Matrix res(n);
for (int i = 0; i < n; i++)
for (int k = 0; k < n; k++)
if (m[i][k])
for (int j = 0; j < n; j++)
res.m[i][j] = (res.m[i][j] + m[i][k] * b.m[k][j]) % MOD;
return res;
}
};
Matrix matrix_pow(Matrix a, long long k) {
Matrix res(a.n);
for (int i = 0; i < a.n; i++) res.m[i][i] = 1; // 单位矩阵
while (k) {
if (k & 1) res = res * a;
a = a * a;
k >>= 1;
}
return res;
}
思路要点:
- 矩阵乘法满足结合律,可用快速幂思想
- 线性递推:构造转移矩阵,乘初始向量
- 图上走k步路径数:邻接矩阵k次方
- 时间复杂度 O(n³ log k)
相关题目: CSG 1076
5.4 最大公约数 / 扩展欧几里得
int gcd(int a, int b) {
while (b) { a %= b; swap(a, b); }
return a;
}
// 扩展欧几里得:求ax + by = gcd(a,b)的解
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 d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
六、计算几何
6.1 基础几何模板
const double eps = 1e-8;
const double PI = acos(-1.0);
int sign(double x) {
if (fabs(x) < eps) return 0;
return x < 0 ? -1 : 1;
}
struct Point {
double x, y;
Point(double x = 0, double y = 0) : x(x), y(y) {}
Point operator+(Point b) { return Point(x + b.x, y + b.y); }
Point operator-(Point b) { return Point(x - b.x, y - b.y); }
Point operator*(double k) { return Point(x * k, y * k); }
double operator*(Point b) { return x * b.x + y * b.y; } // 点积
double operator^(Point b) { return x * b.y - y * b.x; } // 叉积
double len() { return sqrt(x*x + y*y); }
double len2() { return x*x + y*y; }
};
// 叉积:ab × ac
double cross(Point a, Point b, Point c) {
return (b - a) ^ (c - a);
}
// 点积:ab · ac
double dot(Point a, Point b, Point c) {
return (b - a) * (c - a);
}
// 两点距离
double dist(Point a, Point b) {
return (a - b).len();
}
思路要点:
- 点积:判断夹角(锐角/钝角/垂直)、投影长度
- 叉积:判断转向(顺/逆时针)、平行四边形面积
- 浮点比较用 sign 函数,避免精度问题
📏 6.2 点到线段距离 / 线段相交
// 点p到线段ab的距离
double point_to_seg(Point p, Point a, Point b) {
if (sign(dot(a, b, p)) < 0) return dist(p, a);
if (sign(dot(b, a, p)) < 0) return dist(p, b);
return fabs(cross(a, b, p)) / dist(a, b);
}
// 判断线段相交(规范相交)
bool seg_intersect(Point a1, Point a2, Point b1, Point b2) {
double c1 = cross(a1, a2, b1), c2 = cross(a1, a2, b2);
double c3 = cross(b1, b2, a1), c4 = cross(b1, b2, a2);
return sign(c1) * sign(c2) < 0 && sign(c3) * sign(c4) < 0;
}
📚 相关题目:
- CSG 1049, 1098, 1123, 1143, 1114
6.3 凸包(Andrew 算法)
vector<Point> convex_hull(vector<Point> p) {
int n = p.size(), k = 0;
if (n <= 1) return p;
sort(p.begin(), p.end(), [](Point a, Point b) {
return sign(a.x - b.x) ? a.x < b.x : a.y < b.y;
});
vector<Point> res(n * 2);
// 下凸壳
for (int i = 0; i < n; res[k++] = p[i++])
while (k >= 2 && sign(cross(res[k-2], res[k-1], p[i])) <= 0) k--;
// 上凸壳
for (int i = n - 2, t = k + 1; i >= 0; res[k++] = p[i--])
while (k >= t && sign(cross(res[k-2], res[k-1], p[i])) <= 0) k--;
res.resize(k - 1);
return res;
}
思路要点:
- 按 x 排序,x 相同按 y
- 从左到右扫下凸壳,右到左扫上凸壳
- 栈内维护凸性,不满足则弹出
- 时间复杂度 O(n log n)
相关题目: CSG 1068
七、搜索与博弈
7.1 BFS 广度优先搜索
适用场景: 最短步数、最少操作、迷宫最短路
int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};
int dist[N][N];
int bfs(int sx, int sy, int tx, int ty, int n, int m, vector<string>& g) {
memset(dist, -1, sizeof dist);
queue<pair<int, int>> q;
dist[sx][sy] = 0;
q.push({sx, sy});
while (!q.empty()) {
auto [x, y] = q.front(); q.pop();
if (x == tx && y == ty) return dist[x][y];
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (dist[nx][ny] != -1 || g[nx][ny] == '#') continue;
dist[nx][ny] = dist[x][y] + 1;
q.push({nx, ny});
}
}
return -1;
}
思路要点:
- 队列实现,层序遍历
- 首次到达即为最短距离
- 必须判重(visited / dist数组)
- 时间复杂度 O(V + E)
相关题目: CSG 1037, 1038, 1065, 1131, 1195
7.2 DFS 回溯
适用场景: 枚举所有方案、排列组合、剪枝搜索
int ans = 0;
int path[N];
void dfs(int depth, int state) {
if (depth == n) {
ans++; // 找到一个合法方案
return;
}
for (int i = 0; i < n; i++) {
if (check(state, i)) { // 判断是否可选
path[depth] = i;
dfs(depth + 1, state | (1 << i));
// 回溯(path会被覆盖,有时需要显式撤销)
}
}
}
思路要点:
- 递归深入,到底后回溯
- 剪枝:可行性剪枝、最优性剪枝
- 状态压缩:用位运算加速判重
相关题目: CSG 1051, 1128, 1158
7.3 SG 函数 / Nim 博弈
适用场景: 公平组合游戏、多堆石子
int sg[N];
bool vis[N];
// 求单堆SG函数
void get_sg(int n, vector<int>& s) { // s为可选操作集合
sg[0] = 0;
for (int i = 1; i <= n; i++) {
memset(vis, 0, sizeof vis);
for (int op : s) {
if (i >= op) vis[sg[i - op]] = true;
}
for (int j = 0; ; j++) // mex
if (!vis[j]) { sg[i] = j; break; }
}
}
// 多游戏总SG = 各堆SG异或和
// 总SG != 0 先手胜,= 0 后手胜
思路要点:
- SG(x) = mex{ SG(y) | y是x的后继状态 }
- mex:最小的非负整数
- 多个独立游戏:总SG = 各SG异或
- Nim游戏:每堆SG就是石子数
相关题目: CSG 1039, 1214
八、🤔 贪心
8.1 区间调度 / 不重叠区间最多
// 按右端点排序,依次选不重叠的
int max_non_overlap(vector<pair<int, int>>& intervals) {
sort(intervals.begin(), intervals.end(),
[](auto& a, auto& b) { return a.second < b.second; });
int cnt = 0, last = -1e9;
for (auto& [l, r] : intervals) {
if (l >= last) {
cnt++;
last = r;
}
}
return cnt;
}
相关题目: CSG 1090
8.2 田忌赛马
// 贪心策略:
// 1. 最慢赢最慢,赢一场
// 2. 最快赢最快,赢一场
// 3. 最慢碰最快,输一场(消耗对方最快)
int tianji_horse(vector<int>& a, vector<int>& b) {
sort(a.begin(), a.end());
sort(b.begin(), b.end());
int la = 0, ra = a.size() - 1;
int lb = 0, rb = b.size() - 1;
int win = 0;
while (la <= ra) {
if (a[ra] > b[rb]) { win++; ra--; rb--; } // 最快赢最快
else if (a[la] > b[lb]) { win++; la++; lb++; } // 最慢赢最慢
else {
if (a[la] < b[rb]) win--; // 最慢碰最快
la++; rb--;
}
}
return win;
}
相关题目: CSG 1104
九、二分与三分
9.1 二分答案
适用场景: 最大化最小值、最小化最大值(答案单调)
// 最大化最小值:找最大的mid使得check(mid)为真
int binary_search_max(int l, int r) {
while (l < r) {
int mid = (l + r + 1) >> 1; // 上取整!
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
// 最小化最大值:找最小的mid使得check(mid)为真
int binary_search_min(int l, int r) {
while (l < r) {
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
思路要点:
- 答案具有单调性:可行→更大/更小都可行
- check函数验证某个答案是否可行
- 注意上下取整,避免死循环
- 浮点数二分:固定次数或 r-l < eps
相关题目: CSG 1050, 1060, 1222
9.2 三分求极值
适用场景: 单峰函数求最大值/最小值
// 三分求最小值(凸函数)
double ternary_search(double l, double r) {
for (int i = 0; i < 100; i++) { // 迭代100次精度足够
double m1 = l + (r - l) / 3;
double m2 = r - (r - l) / 3;
if (f(m1) < f(m2)) r = m2;
else l = m1;
}
return f(l);
}
思路要点:
- 单峰(凸)函数才能用
- 取两个三等分点比较,缩小区间
- 通常迭代100次精度足够
- 整数三分注意边界
相关题目: CSG 1099, 1050
附录:题目知识点速查表
| 题号 | 题目名称 | 主要知识点 |
|---|---|---|
| CSG 1035 | 一二三 | 模拟/字符串 |
| CSG 1036 | 报数游戏 | 模拟/数学 |
| CSG 1037 | 多连块拼图 | 搜索/模拟 |
| CSG 1038 | 多连块分解 | 搜索/枚举 |
| CSG 1039 | 盒子游戏 | 博弈/找规律 |
| CSG 1040 | 打怪升级 | DP/记忆化 |
| CSG 1041 | 最优对称路径 | 搜索/DP |
| CSG 1042 | Pieces and Discs | 计算几何 |
| CSG 1043 | Super Poker | 生成函数/背包 |
| CSG 1044 | Super Poker II | 背包/数论 |
| CSG 1045 | RMQ with Shifts | 线段树/RMQ |
| CSG 1046 | 汽水瓶 | 数学/模拟 |
| CSG 1047 | 弟弟的作业 | 模拟 |
| CSG 1048 | 数字整除 | 数论/数位 |
| CSG 1049 | 台球碰撞 | 计算几何/模拟 |
| CSG 1050 | 内部收益率 | 二分/数学 |
| CSG 1051 | Biggest Number | DFS/贪心 |
| CSG 1052 | Repairing a Road | 最短路 |
| CSG 1053 | 射击游戏 | DP/计数 |
| CSG 1054 | 战场的数目 | DP/组合数学 |
| CSG 1055 | Infinite Dictionaries | 字典树/模拟 |
| CSG 1056 | Tetrahedrons and Spheres | 三维几何 |
| CSG 1057 | 三家人 | 思维/数学 |
| CSG 1058 | 机器人的指令 | 模拟 |
| CSG 1059 | Updating a Dictionary | 模拟/字符串 |
| CSG 1060 | 平方根大搜索 | 二分/高精度 |
| CSG 1061 | 最短的名字 | Trie/字符串 |
| CSG 1062 | Kingdoms | 图论/最小割 |
| CSG 1063 | 网格中的三角形 | 计算几何/计数 |
| CSG 1064 | Tin Cutter II | 计算几何 |
| CSG 1065 | Collecting Coins | BFS/搜索 |
| CSG 1066 | 病毒 | 字符串/LCS |
| CSG 1067 | Cross-Shaped Tests | 差分/贪心 |
| CSG 1068 | 安全区域 | 三维凸包 |
| CSG 1069 | 全1子矩阵 | DP/单调栈 |
| CSG 1070 | 组合数 | 组合数学 |
| CSG 1071 | Distinct Substrings | 后缀自动机 |
| CSG 1072 | Modulo Nine | 数位DP |
| CSG 1073 | Numbers | 字符串/枚举 |
| CSG 1074 | 4 Buttons | 数学/构造 |
| CSG 1075 | 字典序 | 排列/贪心 |
| CSG 1076 | 有向图 | 矩阵快速幂 |
| CSG 1077 | 2019 | 树/异或 |
| CSG 1078 | Parity of Tuples | 位运算/FWT |
| CSG 1079 | 双向链表练习题 | 链表/模拟 |
| CSG 1080 | 2018 | 计数/DP |
| CSG 1081 | 时间旅行 | 贪心/数学 |
| CSG 1082 | 卖萌表情 | 模拟/字符串 |
| CSG 1083 | Grid | 图论/计数 |
| CSG 1084 | Fixed Point | 置换/数学 |
| CSG 1085 | 排列 | 贪心/构造 |
| CSG 1086 | 千万别用树套树 | 线段树/树状数组 |
| CSG 1087 | Steiner Tree | 斯坦纳树/状压DP |
| CSG 1088 | 买一送一 | 树/统计 |
| CSG 1089 | Use FFT | FFT/多项式 |
| CSG 1090 | 2020 | 贪心/字符串 |
| CSG 1091 | 2020 vs 2018 | 模拟/字符识别 |
| CSG 1092 | Absolute Difference Equation | 数学/找规律 |
| CSG 1093 | String Commutativity | 字符串/数论 |
| CSG 1094 | Make Rounddog Very Happy | 单调栈/二分 |
| CSG 1095 | 抄板子 | NTT/多点求值 |
| CSG 1096 | 奇矩阵 | 构造/线性代数 |
| CSG 1097 | 矩形并 | 计算几何 |
| CSG 1098 | 共线点 | 计算几何 |
| CSG 1099 | Minimum Spanning Tree | MST/三分 |
| CSG 1100 | Spanning Trees | 生成树计数 |
| CSG 1101 | Seating Arrangement | 贪心/DP |
| CSG 1102 | Simplified Blackjack | 概率DP |
| CSG 1103 | Taking Photo | 贪心/排序 |
| CSG 1104 | Tian Ji’s Horse Race Again | 贪心 |
| CSG 1105 | Directed Chain Decomposition | 树/DP |
| CSG 1106 | Forgot Password | 图论/推理 |
| CSG 1108 | Finding Words | 字符串/枚举 |
| CSG 1109 | Nearest Maintenance Point | 多源最短路 |
| CSG 1110 | Monitoring Target | 计算几何 |
| CSG 1111 | Football Training Camp | 模拟/数学 |
| CSG 1112 | Aerial Tramway | 计算几何 |
| CSG 1113 | 大还是小? | 模拟/字符串 |
| CSG 1114 | 多边形的公共部分 | 计算几何 |
| CSG 1115 | 错误的算法 | 思维/构造 |
| CSG 1116 | 简单的图论问题? | 最短路/Dijkstra |
| CSG 1117 | 阶乘除法 | 数论/因数分解 |
| CSG 1118 | Graph Guessing | 图论/概率 |
| CSG 1119 | 聊天止于呵呵 | 字符串/模拟 |
| CSG 1120 | Internet of Lights and Switches | 位运算/哈希 |
| CSG 1121 | 又一道简单题 | 枚举/数学 |
| CSG 1122 | Keep Fit! | DP/前缀和 |
| CSG 1123 | 点到圆弧的距离 | 计算几何 |
| CSG 1124 | 积木玩具 | 三维/计算几何 |
| CSG 1125 | 酷酷的单词 | 字符串/模拟 |
| CSG 1127 | 超大型LED显示屏 | 模拟 |
| CSG 1128 | 地图的四着色 | DFS/计数 |
| CSG 1129 | Giving directions to the tree | 树/拓扑 |
| CSG 1130 | Happy Robot | 模拟 |
| CSG 1131 | 残缺的棋盘 | BFS/数学 |
| CSG 1132 | Just another pachinko-like machine | 概率/DP |
| CSG 1133 | Kick the ball! | 概率/模拟 |
| CSG 1134 | 2016 | 数论/容斥 |
| CSG 1136 | Three Capitals | 图论/数学 |
| CSG 1137 | Toll | 分层图最短路 |
| CSG 1138 | 最长上升子序列 | DP/计数 |
| CSG 1139 | 地铁 | 最短路 |
| CSG 1140 | Parenthesis | 括号/前缀和 |
| CSG 1141 | Reverse | 数学/数位 |
| CSG 1142 | Tree Intersection | 树/启发式合并 |
| CSG 1143 | 三角形和矩形 | 计算几何 |
| CSG 1144 | 盖房子 | DP/计数 |
| CSG 1157 | 12强赛 | 模拟 |
| CSG 1158 | 方格填数 | 组合/杨氏矩阵 |
| CSG 1159 | Average of Two Numbers | 双指针/枚举 |
| CSG 1160 | Sum Them | 数学/公式 |
| CSG 1161 | Not the Only Tree | BST/计数 |
| CSG 1162 | 最大的数 | 贪心/交换 |
| CSG 1163 | 洞穴探宝 | 背包/搜索 |
| CSG 1164 | Circle Intersection | 三维几何 |
| CSG 1165 | 戴森球计划 | 计算几何/积分 |
| CSG 1166 | String Set | 字符串/Trie |
| CSG 1167 | 双排积木 | DP/计数 |
| CSG 1189 | 一起坐火车 | 网络流 |
| CSG 1190 | Complex Maze | 计算几何/最短路 |
| CSG 1191 | Phalanx Dance | 差分/数据结构 |
| CSG 1192 | Big Matrix | 数学/矩阵 |
| CSG 1193 | 走,做核酸去! | 最短路/时间依赖 |
| CSG 1194 | 黑白跳棋 | 博弈/BFS |
| CSG 1195 | 华容道 | BFS/状态搜索 |
| CSG 1197 | 共现的数 | 数据结构/统计 |
| CSG 1198 | Fibonacci Cane | 数学/斐波那契 |
| CSG 1199 | Substrings Same as Prefix | KMP/前缀函数 |
| CSG 1213 | 开开心心233 | 模拟/二分 |
| CSG 1214 | square game | 博弈/SG函数 |
| CSG 1215 | 室温超导 | 字符串/KMP |
| CSG 1216 | Container Orders | 贪心/数论 |
| CSG 1217 | ytree | 数据结构/树 |
| CSG 1218 | 宝石交易 | 图论/最短路 |
| CSG 1219 | 套娃收纳 | 贪心/DP |
| CSG 1221 | hard math | 数学/数论 |
| CSG 1222 | radius | 三维几何/二分 |
| CSG 1223 | 毕业季 | 图论/DP |
| CSG 1321 | 贪吃蛇 | 模拟/贪心 |
| CSG 1322 | HolyK’s Land | 树/图论 |
| CSG 1323 | easy math | 数学/对数 |
| CSG 1324 | Too much noise! | 分类/曼哈顿距离 |
| CSG 1325 | 拼接串 | 字符串/双指针 |
| CSG 1326 | 阅读理解 | DFS/树性质 |
| CSG 1328 | 经文 | 字符串/KMP |
| CSG 1329 | 数据检索系统 | 哈希/布隆过滤器 |
| CSG 1330 | Beautiful Sequence | 滑动窗口/数学 |
| CSG 1331 | 渡劫 | 概率/期望DP |
使用建议:
- 每个知识点先理解思路,再背模板代码
- 模板要能默写,比赛时直接套用修改
- 做完对应题号的题目巩固,注意变式题的坑点
- 重点掌握:Dijkstra、线段树、DP各类、KMP、并查集、快速幂、二分答案 —— 这些是最高频考点

7510

被折叠的 条评论
为什么被折叠?



