NOIP 专题 · 全国青少年信息学奥林匹克联赛(提高组)

NOIP 提高组 省级联赛

NOIP 由 CCF 主办,各省独立评奖,一般每年 11 月举行。它题量大、难度高、时间紧,一场比赛就是四个小时的高强度输出。对大多数学生来说,NOIP 是「检验平时积累」的舞台,而不是「临时抱佛脚」能解决的事。本专题按六大模块拆解考点,并给出从 CSP-S 到 NOIP 的完整备赛路径。

一、赛事概览与参赛规则

项目说明
全称全国青少年信息学奥林匹克联赛(National Olympiad in Informatics in Provinces)
主办方中国计算机学会(CCF),各省赛区承办
面向对象高中及初中生,编程与算法基础扎实者
编程语言C++( 本站聚焦 C++ 方向)
举行时间一般每年 11 月举行(具体以 CCF 当年公告为准)
评奖方式各省独立评奖,省内划定一等奖 / 二等奖分数线
晋级路径部分省份 NOIP 成绩优异者可获得后续省选 / 国赛资格
NOIP 与 CSP-S 的关键差异

CSP-S 第二轮通常 3 小时 4 道题;NOIP 往往时间更紧、题量更多,且几乎不考纯概念,全部是算法设计。没有长期积累的学生,往往第一题能拿分、后面直接崩盘。

二、网络流入门

网络流是 NOIP 的「招牌考点」,出现频率极高,但复杂度相对可控,是性价比很高的一块。

模型解决的问题关键转化
最大流带容量限制的最优运输/分配拆点限制容量,把分配问题建成图
最小割与最大流对偶,常用于「删最少边使某两点不连通」答案 = 最大流;建图时注意拆点与 INF 边
费用流带费用的最优方案,如「每单位流量有不同成本」反向边费用取负,用 SPFA 增广
最大权闭合图有依赖关系的选择问题依赖关系用 INF 边连向被依赖方,再求最大权闭合
上下界可行流流量有最小 / 最大限制加边转化后跑一次流判断可行性
Dinic 最大流(模板,NOIP 常用)
#include <bits/stdc++.h>
using namespace std;

const int N = 100010, M = 200010;

int n, m, S, T;
struct Edge {
  int to, next, cap;
} edges[M];
int head[N], idx = 0;
int level[N], cur[N];

void addEdge(int u, int v, int cap) {
  edges[idx] = {v, head[u], cap};
  head[u] = idx++;
}

bool bfs() {
  memset(level, -1, sizeof(level));
  queue<int> q;
  level[S] = 0; q.push(S);
  while (!q.empty()) {
    int u = q.front(); q.pop();
    for (int i = head[u]; i != -1; i = edges[i].next) {
      int v = edges[i].to;
      if (edges[i].cap > 0 && level[v] == -1) {
        level[v] = level[u] + 1;
        q.push(v);
      }
    }
  }
  return level[T] != -1;
}

int dfs(int u, int flow) {
  if (u == T) return flow;
  for (int &i = cur[u]; i != -1; i = edges[i].next) {
    int v = edges[i].to;
    if (edges[i].cap > 0 && level[v] == level[u] + 1) {
      int pushed = dfs(v, min(flow, edges[i].cap));
      if (pushed > 0) {
        edges[i].cap -= pushed;
        edges[i ^ 1].cap += pushed;   // 反向边(i^1 配对)
        return pushed;
      }
    }
  }
  return 0;
}

int dinic(int s, int t) {
  S = s; T = t;
  int ans = 0;
  while (bfs()) {
    memcpy(cur, head, sizeof(head));
    int f;
    while ((f = dfs(S, 1e9)) > 0) ans += f;
  }
  return ans;
}

int main() {
  fill(head, head + N, -1);
  cin >> n >> m >> S >> T;
  for (int i = 1; i <= m; i++) {
    int u, v, cap; cin >> u >> v >> cap;
    addEdge(u, v, cap);
    addEdge(v, u, 0);      // 必须加反向边
  }
  cout << dinic(S, T) << '\n';
  return 0;
}
网络流三大坑
  • 忘记加反向边——用 edges[i ^ 1] 配对时,加边函数必须一次加两条(容量一正一零)。
  • INF 设置不当:拆点做最小割时 INF 太小会误割,INF 要大于可能的最大流量。
  • 递归层数:网络流递归深度大,节点上万时可能要改成非递归写法。

三、进阶数据结构

数据结构解决什么备注
树链剖分路径查询 / 路径修改 + 线段树,支持任意树上操作NOIP 高频,建树和重链剖分要熟练
可持久化线段树区间第 K 小、区间不同数个数、主席树经典应用只需会写「插入产生新版本」即可
平衡树动态第 K 大、操作序列的最值了解 Treap 旋转思想即可,Splay 偏难
扫描线二维偏序、矩形面积 / 周长统计线段树 + 离散化,套路题居多

四、字符串进阶

相比 CSP-S 只多出后缀数组和后缀自动机两块,这两块理解成本高,但一旦模板化就是稳定得分点。

算法应用
后缀数组求最长重复子串、子串去重比较、LCP / RMQ 配合解 LCP 查询
后缀自动机不同的子串个数、字符串的最小表示、复杂字符串统计
字符串哈希进阶用双哈希把字符串比较降到 O(1),与二分结合做最长公共前缀
学习建议建议:先把后缀数组的倍增法吃透,后缀自动机可作为进阶选学。NOIP 里字符串题常和哈希、二分组合出现,掌握双哈希 + 二分往往比学 SAM 更划算。

五、DP 与图论优化

优化手段适用场景核心思想
斜率优化DP 转移可化为 max/min 的一次函数用单调队列维护凸包,均摊 O(1)
四边形不等式区间划分类 DP(石子合并、括号匹配)限制决策单调性,O(n²) 优化到 O(n log n) 或 O(n²)
插头 DP网格 / 棋盘类路径计数与最小代价用二进制或括号序列记录轮廓线连通性
DP 套 DP多个独立子问题共享 DP 结构把一个 DP 当作另一个 DP 的转移函数
最短路进阶分治最短路、矩阵快速幂优化最短路适用于节点数小、边权特殊的情形
现实建议

这些优化 NOIP 会出现,但往往不是「必须做出来才能得分」的题。基础 DP / 最短路写对,答案已经拿了大头;优化题放最后,用「能推则推、推不出就拿暴力分」的策略。不要因为死磕优化题而丢掉前面的基础分。

六、数学进阶

知识点典型应用
莫比乌斯反演整除关系计数、GCD 相关求和,是 NOIP 数论题的主力工具
生成函数计数与组合问题的系统性求解,进阶选学
FFT / NTT大整数乘法、多项式卷积
组合数学进阶容斥、组合数取模、卡特兰数、错位排列等
提示莫比乌斯反演是这四块里最值得优先攻的——它的套路相对固定,学会了在多道题里都能复用。FFT 和生成函数投入产出比偏低,除非已有扎实基础,否则可以先放一放。

七、计算几何基础

知识点说明
向量与叉积判断点在多边形内、线段相交的基础工具
凸包Andrew 算法 O(n log n) 求凸包,配合旋转卡壳解决直径 / 最远点对
面积公式Shoelace 公式求多边形面积;点在多边形内判定(射线法 / 叉积法)
Andrew 凸包(模板,O(n log n))
#include <bits/stdc++.h>
using namespace std;

struct Pt {
  double x, y;
  bool operator < (const Pt& o) const {
    return x == o.x ? y < o.y : x < o.x;
  }
};

double cross(const Pt& a, const Pt& b, const Pt& c) {
  return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}

vector<Pt> convexHull(vector<Pt> pts) {
  sort(pts.begin(), pts.end());
  int n = pts.size();
  if (n <= 1) return pts;

  vector<Pt> hull(2 * n);
  int k = 0;
  // 下凸壳
  for (int i = 0; i < n; i++) {
    while (k >= 2 && cross(hull[k - 2], hull[k - 1], pts[i]) <= 0) k--;
    hull[k++] = pts[i];
  }
  // 上凸壳
  int t = k + 1;
  for (int i = n - 2; i >= 0; i--) {
    while (k >= t && cross(hull[k - 2], hull[k - 1], pts[i]) <= 0) k--;
    hull[k++] = pts[i];
  }
  hull.resize(k - 1);   // 首尾点重合,去掉一个
  return hull;
}

八、备赛路径与能力自测

从 CSP-S 到 NOIP 的四个阶段

  1. 补齐前置:完整学完 CSP-S 专题 全部模块,确保树状数组、线段树、最短路、Tarjan、基础 DP 能独立写出。这一步不能省。
  2. 主攻网络流:最大流 → 最小割 → 费用流 → 最大权闭合图,每类至少 5 题,网络流是 NOIP 最稳的得分点。
  3. 补进阶结构与优化:树链剖分、主席树、扫描线;DP 优化按「斜率 → 四边形 → 插头」的顺序推进。
  4. 数学与几何选学:优先莫比乌斯反演,其次凸包 + 旋转卡壳;FFT / 生成函数按需选学。
  5. 限时实战:找历年真题按真实考场时间完整模拟至少 5 套,训练「读题 → 建模 → 实现 → 检查」的完整节奏。

能力自测(能默写模板才算过关)

序号自测项掌握度
1Dinic 最大流模板,能解释当前弧优化的作用□ 熟练 □ 看模板 □ 没思路
2把「最少删边使两点不连通」转成最小割建模□ 熟练 □ 看模板 □ 没思路
3费用流求带成本的最优分配□ 熟练 □ 看模板 □ 没思路
4树链剖分 + 线段树支持路径修改查询□ 熟练 □ 看模板 □ 没思路
5莫比乌斯反演解决整除关系计数□ 熟练 □ 看模板 □ 没思路
6Andrew 凸包,能用它求面积和最远点对□ 熟练 □ 看模板 □ 没思路
7斜率优化:把 O(n²) DP 压到 O(n)□ 熟练 □ 看模板 □ 没思路
8能独立分析一道 NOIP 题的模型与复杂度□ 熟练 □ 看模板 □ 没思路

九、赛场失分点与策略

NOIP 赛场上最可惜的丢分
  • 读题太慢:四小时十几道题,第一遍读题务必留下标记,先做有把握的。
  • 卡在一道题上死磕:单题超过 40 分钟没进展就果断跳过,回头再攻。会的题永远比不会的多。
  • 复杂度没估:n = 1e5 却写 O(n²),写完才发现是 TLE。写代码前先算复杂度。
  • 忘开 long long:乘法溢出是 NOIP 的经典送命点。
  • 递归爆栈:链状结构深度到 1e5,递归会爆栈,改迭代。
  • 最后一小时没检查:至少留 15 分钟通读代码,看有没有笔误、数组开小、忘多测。
一句话总结

NOIP 拼的是基础模型的熟练度 + 建模速度 + 稳定的临场发挥。与其追求冷僻算法的覆盖,不如把树状数组、线段树、网络流、Tarjan、基础 DP 这几块练到闭着眼睛都能写出来。相关应试细节可回看 20 机考应对与坑点。

十、NOIP 配套模拟题

下面 20 道题按单选 → 程序阅读 → 编程三类组织,覆盖网络流、树剖、DP 优化、 莫比乌斯、计算几何等进阶考点。题库为教学自编,用于配套练习与自测,不是官方历年真题。