A · 贪心与双指针
A1. 反悔贪心 · 带截止时间的任务调度 #
按截止时间升序,报酬先入小根堆;已选数量超过当前截止 d 就弹出最小报酬(反悔)。
完整代码(P2949 可直接提交)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n; scanf("%d", &n);
vector<pair<long long, long long>> a(n); // (d, p)
for (int i = 0; i < n; i++) scanf("%lld %lld", &a[i].first, &a[i].second);
sort(a.begin(), a.end());
priority_queue<long long, vector<long long>, greater<long long>> pq;
long long ans = 0;
for (int i = 0; i < n; i++) {
long long d = a[i].first, p = a[i].second;
pq.push(p); ans += p;
if ((long long)pq.size() > d) { ans -= pq.top(); pq.pop(); }
}
printf("%lld\n", ans);
return 0;
}A2. 反悔贪心 · 链表 + 堆(不相邻选取) #
大根堆选最大 a[i];选后将 i 与邻居合并为权值 a[l]+a[r]−a[i] 的"悔棋子"重新入堆;堆顶 ≤ 0 停止(至多 k 个)。
完整代码(P1484 可直接提交)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 500005;
ll a[N]; int L[N], R[N]; bool del[N];
int main() {
int n, k; scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
priority_queue<pair<ll, int>> pq;
for (int i = 1; i <= n; i++) { L[i] = i - 1; R[i] = i + 1; pq.push({a[i], i}); }
a[0] = a[n + 1] = LLONG_MIN / 2; // 两端哨兵
ll ans = 0;
for (int t = 0; t < k; t++) {
auto [v, i] = pq.top(); pq.pop();
if (del[i]) { t--; continue; } // 懒删除
if (v <= 0) break; // 至多 k 棵:再选不赚
ans += v;
int l = L[i], r = R[i];
a[i] = a[l] + a[r] - a[i]; // 悔棋子
del[l] = del[r] = true;
L[i] = L[l]; R[i] = R[r];
R[L[i]] = i; L[R[i]] = i;
pq.push({a[i], i});
}
printf("%lld\n", ans);
return 0;
}A3. 区间贪心(选点 / 覆盖 / 调度) #
选点:按右端点排序,未覆盖就在右端点放点;覆盖:左端点 ≤ 当前位置中选右端点最远的;调度:按右端点排序能选就选。
完整代码(区间选点骨架)
#include <bits/stdc++.h>
using namespace std;
struct Seg { long long l, r; } seg[200005];
int main() {
int n; scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%lld%lld", &seg[i].l, &seg[i].r);
sort(seg + 1, seg + n + 1, [](Seg a, Seg b){ return a.r < b.r; });
int ans = 0; long long last = LLONG_MIN;
for (int i = 1; i <= n; i++)
if (seg[i].l > last) { ans++; last = seg[i].r; }
printf("%d\n", ans);
return 0;
}A4. 双指针配对(决斗模型) #
严格大者淘汰对方、各限一次 → 最大配对数:排序后双指针,最弱者配"能赢它的最弱者"。答案 = n − 配对数。
完整代码(决斗配对骨架)
#include <bits/stdc++.h>
using namespace std;
int a[200005];
int main() {
int n; scanf("%d", &n);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
sort(a, a + n);
int match = 0;
for (int i = 0, j = n / 2; i < n / 2 && j < n; j++)
if (a[j] > a[i]) { match++; i++; } // a[j] 淘汰 a[i]
printf("%d\n", n - match);
return 0;
}B · 字符串
B1. Manacher(最长回文子串 / 回文计数) #
插 # 统一奇偶;臂长 p[i] = min(p[2c−i], R−i) 镜像继承后扩展;原串回文长度 = p[i],回文总数 = Σ(p[i]+1)/2。
完整代码(P3805 可直接提交)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
string s; cin >> s;
string t = "^#";
for (char c : s) { t += c; t += '#'; }
t += '$';
int n = t.size(), C = 0, R = 0, ans = 0;
vector<int> p(n, 0);
for (int i = 1; i < n - 1; i++) {
p[i] = (i < R) ? min(R - i, p[2 * C - i]) : 0;
while (t[i + p[i] + 1] == t[i - p[i] - 1]) p[i]++;
if (i + p[i] > R) { C = i; R = i + p[i]; }
ans = max(ans, p[i]);
}
cout << ans << "\n";
return 0;
}B2. 字符串哈希(子串相等 / 判回文 / LCP) #
B 进制前缀哈希;子串哈希 = h[r] − h[l−1]·B^(r−l+1);字符映射 +1 防前导零;判回文用正反对称两份哈希。
完整代码(模板)
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N = 1000005;
const ull B = 131;
ull h[N], p[N]; char s[N];
ull get(int l, int r) { return h[r] - h[l - 1] * p[r - l + 1]; } // 1-based
int main() {
scanf("%s", s + 1);
int n = strlen(s + 1);
p[0] = 1;
for (int i = 1; i <= n; i++) {
p[i] = p[i - 1] * B;
h[i] = h[i - 1] * B + (s[i] - 'a' + 1);
}
// 判 s[l..r] 回文:再对反串建一份哈希 revh,比较 get(l,r) 与 revh.get(n-r+1, n-l+1)
return 0;
}B3. KMP(单模式匹配 / next 数组) #
next[j] = 前缀的最长公共真前后缀;失配时模式右滑、文本指针永不回退。是 AC 自动机 fail 指针的一维原型。
完整代码(模板)
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005;
int nxt[N]; char s[N], p[N];
int main() {
scanf("%s %s", s, p); // 文本 s、模式 p
int n = strlen(s), m = strlen(p);
nxt[0] = -1;
for (int i = 1, j = -1; i < m; i++) {
while (j >= 0 && p[i] != p[j + 1]) j = nxt[j];
if (p[i] == p[j + 1]) j++;
nxt[i] = j;
}
for (int i = 0, j = -1; i < n; i++) {
while (j >= 0 && s[i] != p[j + 1]) j = nxt[j];
if (s[i] == p[j + 1]) j++;
if (j == m - 1) { printf("%d\n", i - m + 2); j = nxt[j]; }
}
return 0;
}B4. AC 自动机(多模式匹配 + fail 树统计) #
Trie + BFS 建 fail(顺带转移压缩);扫描只打标记,逆 BFS 序做 fail 树子树和统计出现次数。
完整代码(P5357 可直接提交)
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int ch[N][26], fail[N], idx = 0;
int pos[N]; long long cnt[N]; int q[N];
int insert_(const char *s, int id) {
int u = 0;
for (; *s; s++) {
int v = *s - 'a';
if (!ch[u][v]) ch[u][v] = ++idx;
u = ch[u][v];
}
return pos[id] = u;
}
void build() {
int h = 0, t = 0;
for (int c = 0; c < 26; c++) if (ch[0][c]) q[t++] = ch[0][c];
while (h < t) {
int u = q[h++];
for (int c = 0; c < 26; c++) {
int v = ch[u][c];
if (v) { fail[v] = ch[fail[u]][c]; q[t++] = v; }
else ch[u][c] = ch[fail[u]][c];
}
}
}
int main() {
int n; scanf("%d", &n);
static char buf[200006];
for (int i = 0; i < n; i++) { scanf("%s", buf); insert_(buf, i); }
build();
static char text[2000006]; scanf("%s", text);
int u = 0;
for (char *p = text; *p; p++) { u = ch[u][*p - 'a']; cnt[u]++; }
for (int i = idx - 1; i >= 0; i--) cnt[fail[q[i]]] += cnt[q[i]];
for (int i = 0; i < n; i++) printf("%lld\n", cnt[pos[i]]);
return 0;
}C · 动态规划
C1. 状压 DP · TSP 旅行商 #
f[S][i] = 已走集合 S、停在 i 的最短路;答案要 +d[i][0] 回起点。n ≤ 20 的识别信号。
完整代码(P1171 可直接提交)
#include <bits/stdc++.h>
using namespace std;
int n, w[20][20], dp[1 << 20][20];
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) scanf("%d", &w[i][j]);
memset(dp, 0x3f, sizeof dp);
dp[1][0] = 0;
for (int S = 1; S < (1 << n); S++)
for (int i = 0; i < n; i++) {
if (!(S >> i & 1) || dp[S][i] == 0x3f3f3f3f) continue;
for (int j = 0; j < n; j++)
if (!(S >> j & 1))
dp[S | 1 << j][j] = min(dp[S | 1 << j][j], dp[S][i] + w[i][j]);
}
int ans = INT_MAX;
for (int i = 0; i < n; i++) ans = min(ans, dp[(1 << n) - 1][i] + w[i][0]);
printf("%d\n", ans);
return 0;
}C2. 完全背包计数 + 容斥(上限约束) #
无上限方案先完全背包预处理;"第 i 种超限"= 强制用 di+1 枚扣减,16 个越界集合容斥。
完整代码(P1450 可直接提交)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f[100005]; int c[4], d[4];
int main() {
for (int i = 0; i < 4; i++) scanf("%d", &c[i]);
f[0] = 1;
for (int i = 0; i < 4; i++)
for (int x = c[i]; x <= 100000; x++) f[x] += f[x - c[i]];
int q; scanf("%d", &q);
while (q--) {
for (int i = 0; i < 4; i++) scanf("%d", &d[i]);
ll s, ans = 0; scanf("%lld", &s);
for (int S = 0; S < 16; S++) {
ll t = s; int bits = 0;
for (int i = 0; i < 4; i++)
if (S >> i & 1) { t -= (ll)(d[i] + 1) * c[i]; bits++; }
if (t >= 0) ans += (bits & 1 ? -1 : 1) * f[t];
}
printf("%lld\n", ans);
}
return 0;
}C3. 区间 DP · 计数防重框架 #
计数类区间 DP 的核心是防重:f(两端匹配)/ g(可拆并列)分类,规定"最后一段是完整匹配段"唯一切分。
框架代码(括号序列型骨架)
// f[l][r]:两端恰好匹配的合法序列数;g[l][r]:合法但可拆并列
for (int len = 2; len <= n; len++)
for (int l = 1; l + len - 1 <= n; l++) {
int r = l + len - 1;
if (match(l, r)) { // 两端可配对
f[l][r] = (g[l + 1][r - 1] + 内部星号方案) % mod;
}
// 规定最后一段是完整匹配段,保证不重不漏
for (int k = l; k < r; k++)
g[l][r] = (g[l][r] + (ll)g[l][k] * f[k + 1][r]) % mod;
g[l][r] = (g[l][r] + f[l][r]) % mod;
}C4. 树形 DP / 换根 / 树的重心 #
两遍 dfs:自底向上求 sz 与 f[1],自顶向下 f[v] = f[u] + n − 2·sz[v]。重心:max(子树大小, n−sz[u]) 最小者。
完整代码(P1395 可直接提交)
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
vector<int> g[N];
long long sz[N], f[N];
int n;
void dfs1(int u, int fa, long long d) {
sz[u] = 1; f[1] += d;
for (int v : g[u]) if (v != fa) { dfs1(v, u, d + 1); sz[u] += sz[v]; }
}
void dfs2(int u, int fa) {
for (int v : g[u]) if (v != fa) {
f[v] = f[u] + (long long)(n - sz[v]) - sz[v];
dfs2(v, u);
}
}
int main() {
scanf("%d", &n);
for (int i = 1, x, y; i < n; i++) { scanf("%d%d", &x, &y); g[x].push_back(y); g[y].push_back(x); }
dfs1(1, 0, 0); dfs2(1, 0);
long long best = LLONG_MAX; int id = 0;
for (int i = 1; i <= n; i++)
if (f[i] < best) { best = f[i]; id = i; }
printf("%d %lld\n", id, best);
return 0;
}D · 图论
D1. BFS(可达性 / 最少步数) #
队列按层扩展,入队即打标记;第一次到达即最少步数。
完整代码(模板)
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
vector<int> g[N];
int vis[N], dist[N];
int main() {
int n, m, s; scanf("%d%d%d", &n, &m, &s);
for (int i = 0, u, v; i < m; i++) { scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); }
queue<int> q;
q.push(s); vis[s] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) if (!vis[v]) {
vis[v] = 1; dist[v] = dist[u] + 1; q.push(v);
}
}
return 0;
}D2. Dijkstra(堆优化,非负权单源最短路) #
小根堆每次锁定 dist 最小的未确定点并松弛出边;过期堆顶用 d ≠ dist[u] 懒删除。
完整代码(P4779 可直接提交)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
vector<pair<int, ll>> g[N];
ll dist[N];
int main() {
int n, m, s; scanf("%d%d%d", &n, &m, &s);
for (int i = 0, u, v; i < m; i++) { ll w; scanf("%d%d%lld", &u, &v, &w); g[u].push_back({v, w}); }
memset(dist, 0x3f, sizeof dist); dist[s] = 0;
priority_queue<pair<ll, int>, vector<pair<ll, int>>, greater<>> q;
q.push({0, s});
while (!q.empty()) {
auto [d, u] = q.top(); q.pop();
if (d != dist[u]) continue;
for (auto [v, w] : g[u])
if (d + w < dist[v]) { dist[v] = d + w; q.push({dist[v], v}); }
}
for (int i = 1; i <= n; i++) printf("%lld ", dist[i]);
return 0;
}D3. Floyd(多点对最短路) #
k 必须最外层:"只允许经过前 k 个点"的阶段递进。常作为关键点两两距离的预处理工具。
完整代码(模板)
#include <bits/stdc++.h>
using namespace std;
int f[405][405];
int main() {
int n, m; scanf("%d%d", &n, &m);
memset(f, 0x3f, sizeof f);
for (int i = 1; i <= n; i++) f[i][i] = 0;
for (int i = 0, u, v, w; i < m; i++) {
scanf("%d%d%d", &u, &v, &w);
f[u][v] = f[v][u] = min(f[u][v], w); // 重边取小
}
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
return 0;
}D4. Kruskal 最小生成树 + 并查集 #
边按权排序,并查集逐条合并未连通两端;选满 n−1 条边即最小生成树。
完整代码(P3366 可直接提交)
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int fa[N];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
struct Edge { int u, v, w; } e[N];
int main() {
int n, m; scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
sort(e, e + m, [](Edge a, Edge b){ return a.w < b.w; });
for (int i = 1; i <= n; i++) fa[i] = i;
long long ans = 0; int cnt = 0;
for (int i = 0; i < m && cnt < n - 1; i++) {
int x = find(e[i].u), y = find(e[i].v);
if (x == y) continue;
fa[x] = y; ans += e[i].w; cnt++;
}
if (cnt < n - 1) printf("orz\n");
else printf("%lld\n", ans);
return 0;
}D5. bitset 加速(背包可行性 / 可达性传递) #
0/1 背包:f |= f << w;可达性:reach[v] |= reach[u];交集计数:(a & b).count()。
完整代码(bitset 背包模板)
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
bitset<N> f;
int main() {
int n, m; scanf("%d%d", &n, &m); // n 件物品,问能否凑出 m
f[0] = 1;
for (int i = 0, w; i < n; i++) { scanf("%d", &w); f |= (f << w); }
puts(f[m] ? "YES" : "NO");
return 0;
}E · 数据结构与其他
E1. 莫队(离线区间查询) #
询问按 (l 块号, r 奇偶交替) 排序,双指针滑动,add/remove O(1) 增量维护答案。
完整代码(P2709 骨架)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 50005;
int a[N], cnt[N], n, m, B;
ll ans[N], cur = 0;
struct Q { int l, r, id, blk; } q[N];
void add(int x) { cur += 2 * cnt[x] + 1; cnt[x]++; } // (k+1)²−k² = 2k+1
void del(int x) { cnt[x]--; cur -= 2 * cnt[x] + 1; }
int main() {
scanf("%d%d", &n, &m); B = sqrt(n) + 1;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 0; i < m; i++) {
scanf("%d%d", &q[i].l, &q[i].r);
q[i].id = i; q[i].blk = (q[i].l - 1) / B;
}
sort(q, q + m, [](Q x, Q y){
if (x.blk != y.blk) return x.blk < y.blk;
return (x.blk & 1) ? x.r < y.r : x.r > y.r;
});
for (int i = 0, L = 1, R = 0; i < m; i++) {
while (L > q[i].l) add(a[--L]);
while (R < q[i].r) add(a[++R]);
while (L < q[i].l) del(a[L++]);
while (R > q[i].r) del(a[R--]);
ans[q[i].id] = cur;
}
for (int i = 0; i < m; i++) printf("%lld\n", ans[i]);
return 0;
}E2. 扫描线 + 离散化(矩形面积并) #
矩形拆左右边缘 ±1 事件按 x 排序;y 离散化,线段树维护覆盖长度(叶子是"段",不下传);先算贡献再处理事件。
完整代码(P5490 可直接提交)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100005;
struct Node { int cnt; ll len; } t[8 * N];
struct Ev { ll x, y1, y2; int v; };
vector<Ev> ev; vector<ll> ys;
void pushup(int u, int l, int r) {
if (t[u].cnt) t[u].len = ys[r + 1] - ys[l];
else t[u].len = (l == r) ? 0 : t[u * 2].len + t[u * 2 + 1].len;
}
void update(int u, int l, int r, int ql, int qr, int v) {
if (qr < l || r < ql) return;
if (ql <= l && r <= qr) { t[u].cnt += v; pushup(u, l, r); return; }
int m = (l + r) / 2;
update(u * 2, l, m, ql, qr, v); update(u * 2 + 1, m + 1, r, ql, qr, v);
pushup(u, l, r);
}
int main() {
int n; scanf("%d", &n);
for (int i = 0; i < n; i++) {
ll x1, y1, x2, y2; scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);
ev.push_back({x1, y1, y2, 1}); ev.push_back({x2, y1, y2, -1});
ys.push_back(y1); ys.push_back(y2);
}
sort(ev.begin(), ev.end(), [](auto& a, auto& b){ return a.x < b.x; });
sort(ys.begin(), ys.end()); ys.erase(unique(ys.begin(), ys.end()), ys.end());
int m = ys.size() - 1;
ll ans = 0, lastX = ev[0].x;
for (auto& e : ev) {
ans += t[1].len * (e.x - lastX);
int l = lower_bound(ys.begin(), ys.end(), e.y1) - ys.begin();
int r = lower_bound(ys.begin(), ys.end(), e.y2) - ys.begin() - 1;
if (l <= r) update(1, 0, m - 1, l, r, e.v);
lastX = e.x;
}
printf("%lld\n", ans);
return 0;
}E3. 大模拟与环形同余 #
规则清单 + struct + 分函数;环形位置 (pos + step % n + n) % n;face == dir 下标减,否则加。
完整代码(P1563 可直接提交)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m; scanf("%d%d", &n, &m);
vector<int> face(n); vector<string> name(n);
for (int i = 0; i < n; i++) {
char s[12]; scanf("%d %s", &face[i], s); name[i] = s;
}
int pos = 0;
while (m--) {
int dir; long long s; scanf("%d%lld", &dir, &s);
if (dir == face[pos]) pos = ((pos - s % n) % n + n) % n;
else pos = (pos + s) % n;
}
printf("%s\n", name[pos].c_str());
return 0;
}