考试必备

既然有4小时且不确定考状压,我们需要做更全面的准备。初中生参加NOIP级别的测试,题目通常覆盖动态规划、图论、搜索、数据结构等核心算法。

📊 各算法押题概率分析(5题分布预测)

1. 动态规划综合题(95%)

  • 线性DP/区间DP:最长公共子序列、石子合并等
  • 背包问题变种:多重背包优化、分组背包
  • 树形DP:树上的最大独立集、直径等
  • 状压DP:n≤20的集合选择问题

2. 图论基础题(90%)

  • 最短路:Dijkstra(优先队列优化)
  • 最小生成树:Kruskal或Prim
  • 拓扑排序:任务安排、课程表类问题

3. 数据结构应用(80%)

  • 并查集:带权并查集、扩展域
  • 单调栈/队列:滑动窗口最值
  • 树状数组:区间求和、逆序对

4. 搜索与剪枝(70%)

  • DFS剪枝:可行性剪枝、最优性剪枝
  • BFS状态搜索:八数码、迷宫类
  • 记忆化搜索:DP的递归实现

5. 数学/思维题(60%)

  • 组合数学:排列组合、容斥原理
  • 贪心策略:区间调度、哈夫曼编码
  • 简单数论:GCD、快速幂

🚀 4小时考场最佳策略

时间分配(黄金比例)

0-15分钟:浏览所有题目,评估难度
15-60分钟:完成最熟悉的1-2题(建立信心)
60-180分钟:主攻中等难度题(仔细实现)
180-210分钟:挑战难题部分分
210-240分钟:全面检查(边界、极端数据)

做题顺序建议

  1. 签到题(15分钟内能AC的)
  2. 套路题(熟悉模板的,如图论、DP)
  3. 中等题(需要思考但可实现的)
  4. 难题(写暴力拿部分分)
  5. 检查所有题(输入输出、数组大小)

万能代码库(直接使用)

1. 输入输出优化(必带)

#include <bits/stdc++.h>
using namespace std;

// 快读
inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

// 快写
inline void write(int x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

2. 动态规划(DP)

2.1 最长上升子序列(LIS) - O(n log n)

int LIS(vector<int>& a) {
    int n = a.size();
    vector<int> d;  // 存储当前找到的上升子序列
    for (int i = 0; i < n; i++) {
        // 找到第一个大于等于a[i]的位置
        auto it = lower_bound(d.begin(), d.end(), a[i]);
        if (it == d.end())
            d.push_back(a[i]);   // 如果a[i]比所有都大,则扩展
        else
            *it = a[i];          // 否则替换,使得序列尽可能小
    }
    return d.size();
}

2.2 最长公共子序列(LCS) - O(nm)

int LCS(string& a, string& b) {
    int n = a.size(), m = b.size();
    vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (a[i-1] == b[j-1])
                dp[i][j] = dp[i-1][j-1] + 1;
            else
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
        }
    }
    return dp[n][m];
}

2.3 背包问题

01背包(滚动数组)
int knapsack01(vector<int>& w, vector<int>& v, int capacity) {
    int n = w.size();
    vector<int> dp(capacity+1, 0);
    for (int i = 0; i < n; i++) {
        for (int j = capacity; j >= w[i]; j--) {
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }
    return dp[capacity];
}
完全背包
int completeKnapsack(vector<int>& w, vector<int>& v, int capacity) {
    int n = w.size();
    vector<int> dp(capacity+1, 0);
    for (int i = 0; i < n; i++) {
        for (int j = w[i]; j <= capacity; j++) {
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }
    return dp[capacity];
}

2.4 区间DP(石子合并)

int stoneMerge(vector<int>& stones) {
    int n = stones.size();
    vector<int> prefix(n+1, 0);
    for (int i = 0; i < n; i++) prefix[i+1] = prefix[i] + stones[i];
    
    vector<vector<int>> dp(n, vector<int>(n, INT_MAX));
    for (int i = 0; i < n; i++) dp[i][i] = 0;
    
    for (int len = 2; len <= n; len++) {
        for (int l = 0; 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] + prefix[r+1] - prefix[l]);
            }
        }
    }
    return dp[0][n-1];
}

3. 图论

3.1 最短路径

Dijkstra(优先队列优化) - O((V+E) log V)
const int INF = 0x3f3f3f3f;
void dijkstra(int s, vector<vector<pair<int, int>>>& graph, vector<int>& dist) {
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;
    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] : graph[u]) {
            if (dist[v] > dist[u] + w) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
}
Bellman-Ford(判断负环)
struct Edge {
    int u, v, w;
};
bool bellmanFord(int n, vector<Edge>& edges, int s, vector<int>& dist) {
    dist.assign(n, INF);
    dist[s] = 0;
    for (int i = 0; i < n - 1; i++) {
        for (auto& e : edges) {
            if (dist[e.u] != INF && dist[e.v] > dist[e.u] + e.w) {
                dist[e.v] = dist[e.u] + e.w;
            }
        }
    }
    // 检查负环
    for (auto& e : edges) {
        if (dist[e.u] != INF && dist[e.v] > dist[e.u] + e.w) {
            return false;  // 有负环
        }
    }
    return true;
}
Floyd(多源最短路) - O(n^3)
void floyd(vector<vector<int>>& dist, int n) {
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (dist[i][k] != INF && dist[k][j] != INF)
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
            }
        }
    }
}

3.2 最小生成树

Kruskal
struct Edge {
    int u, v, w;
    bool operator<(const Edge& other) const { return w < other.w; }
};

int find(vector<int>& parent, int x) {
    return parent[x] == x ? x : parent[x] = find(parent, parent[x]);
}

int kruskal(vector<Edge>& edges, int n) {
    sort(edges.begin(), edges.end());
    vector<int> parent(n);
    for (int i = 0; i < n; i++) parent[i] = i;
    
    int total = 0, cnt = 0;
    for (auto& e : edges) {
        int pu = find(parent, e.u), pv = find(parent, e.v);
        if (pu != pv) {
            parent[pu] = pv;
            total += e.w;
            if (++cnt == n - 1) break;
        }
    }
    return cnt == n - 1 ? total : -1;  // 如果不连通返回-1
}

3.3 拓扑排序

vector<int> topologicalSort(int n, vector<vector<int>>& graph, vector<int>& indegree) {
    queue<int> q;
    for (int i = 0; i < n; i++) {
        if (indegree[i] == 0) q.push(i);
    }
    vector<int> order;
    while (!q.empty()) {
        int u = q.front(); q.pop();
        order.push_back(u);
        for (int v : graph[u]) {
            if (--indegree[v] == 0) {
                q.push(v);
            }
        }
    }
    if (order.size() != n) return {};  // 有环
    return order;
}

3.4 强连通分量(Tarjan)

void tarjan(int u, vector<vector<int>>& graph, vector<int>& dfn, vector<int>& low, 
            vector<bool>& inStack, stack<int>& st, int& index, vector<vector<int>>& scc) {
    dfn[u] = low[u] = ++index;
    st.push(u);
    inStack[u] = true;
    for (int v : graph[u]) {
        if (!dfn[v]) {
            tarjan(v, graph, dfn, low, inStack, st, index, scc);
            low[u] = min(low[u], low[v]);
        } else if (inStack[v]) {
            low[u] = min(low[u], dfn[v]);
        }
    }
    if (dfn[u] == low[u]) {
        vector<int> component;
        int v;
        do {
            v = st.top(); st.pop();
            inStack[v] = false;
            component.push_back(v);
        } while (v != u);
        scc.push_back(component);
    }
}

4. 数据结构

4.1 并查集(DSU)

class DSU {
    vector<int> parent, rank;
public:
    DSU(int n) : parent(n), rank(n, 0) {
        for (int i = 0; i < n; i++) parent[i] = i;
    }
    int find(int x) {
        return parent[x] == x ? x : parent[x] = find(parent[x]);
    }
    void unite(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) return;
        if (rank[x] < rank[y]) parent[x] = y;
        else {
            parent[y] = x;
            if (rank[x] == rank[y]) rank[x]++;
        }
    }
    bool same(int x, int y) { return find(x) == find(y); }
};

4.2 树状数组(Fenwick Tree)

class Fenwick {
    vector<int> tree;
    int n;
public:
    Fenwick(int size) : n(size), tree(size + 1, 0) {}
    void update(int idx, int delta) {
        for (; idx <= n; idx += idx & -idx) tree[idx] += delta;
    }
    int query(int idx) {
        int sum = 0;
        for (; idx > 0; idx -= idx & -idx) sum += tree[idx];
        return sum;
    }
    int rangeSum(int l, int r) { return query(r) - query(l - 1); }
};

4.3 线段树(区间求和、区间更新)

class SegmentTree {
    vector<int> tree, lazy;
    int n;
    void build(vector<int>& arr, int node, int start, int end) {
        if (start == end) {
            tree[node] = arr[start];
        } else {
            int mid = (start + end) / 2;
            build(arr, node*2, start, mid);
            build(arr, node*2+1, mid+1, end);
            tree[node] = tree[node*2] + tree[node*2+1];
        }
    }
    void pushDown(int node, int start, int end) {
        if (lazy[node]) {
            int mid = (start + end) / 2;
            tree[node*2] += lazy[node] * (mid - start + 1);
            tree[node*2+1] += lazy[node] * (end - mid);
            lazy[node*2] += lazy[node];
            lazy[node*2+1] += lazy[node];
            lazy[node] = 0;
        }
    }
    void updateRange(int node, int start, int end, int l, int r, int val) {
        if (l > end || r < start) return;
        if (l <= start && end <= r) {
            tree[node] += val * (end - start + 1);
            lazy[node] += val;
            return;
        }
        pushDown(node, start, end);
        int mid = (start + end) / 2;
        updateRange(node*2, start, mid, l, r, val);
        updateRange(node*2+1, mid+1, end, l, r, val);
        tree[node] = tree[node*2] + tree[node*2+1];
    }
    int queryRange(int node, int start, int end, int l, int r) {
        if (l > end || r < start) return 0;
        if (l <= start && end <= r) return tree[node];
        pushDown(node, start, end);
        int mid = (start + end) / 2;
        return queryRange(node*2, start, mid, l, r) + 
               queryRange(node*2+1, mid+1, end, l, r);
    }
public:
    SegmentTree(vector<int>& arr) {
        n = arr.size();
        tree.resize(4*n);
        lazy.resize(4*n, 0);
        build(arr, 1, 0, n-1);
    }
    void update(int l, int r, int val) { updateRange(1, 0, n-1, l, r, val); }
    int query(int l, int r) { return queryRange(1, 0, n-1, l, r); }
};

5. 数学

5.1 快速幂

long long quickPow(long long a, long long b, long long mod) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

5.2 素数判定(Miller-Rabin)

using ll = long long;

ll mul_mod(ll a, ll b, ll mod) {
    return (__int128)a * b % mod;
}

ll pow_mod(ll a, ll b, ll mod) {
    ll res = 1;
    while (b) {
        if (b & 1) res = mul_mod(res, a, mod);
        a = mul_mod(a, a, mod);
        b >>= 1;
    }
    return res;
}

bool millerRabin(ll n) {
    if (n < 3 || n % 2 == 0) return n == 2;
    ll a = n - 1, b = 0;
    while (a % 2 == 0) a /= 2, ++b;
    // 测试的底数,可以足够判断2^64内的素数
    vector<ll> test_nums = {2, 325, 9375, 28178, 450775, 9780504, 1795265022};
    for (ll x : test_nums) {
        if (x % n == 0) continue;
        ll v = pow_mod(x, a, n);
        if (v == 1 || v == n - 1) continue;
        int j;
        for (j = 0; j < b; ++j) {
            v = mul_mod(v, v, n);
            if (v == n - 1) break;
        }
        if (j >= b) return false;
    }
    return true;
}

5.3 最大公约数(GCD)和最小公倍数(LCM)

int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
int lcm(int a, int b) { return a / gcd(a, b) * b; }

5.4 扩展欧几里得(解 ax + by = gcd(a,b))

int exgcd(int a, int b, int &x, int &y) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return d;
}

6. 字符串

6.1 KMP(字符串匹配)

vector<int> getNext(string& pattern) {
    int n = pattern.size();
    vector<int> next(n, 0);
    for (int i = 1, j = 0; i < n; i++) {
        while (j > 0 && pattern[i] != pattern[j]) j = next[j-1];
        if (pattern[i] == pattern[j]) j++;
        next[i] = j;
    }
    return next;
}

vector<int> kmp(string& text, string& pattern) {
    vector<int> next = getNext(pattern);
    vector<int> positions;
    int n = text.size(), m = pattern.size();
    for (int i = 0, j = 0; i < n; i++) {
        while (j > 0 && text[i] != pattern[j]) j = next[j-1];
        if (text[i] == pattern[j]) j++;
        if (j == m) {
            positions.push_back(i - m + 1);
            j = next[j-1];
        }
    }
    return positions;
}

7. 其他

7.1 离散化

vector<int> discretize(vector<int>& a) {
    vector<int> b = a;
    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());
    for (int& x : a) {
        x = lower_bound(b.begin(), b.end(), x) - b.begin() + 1; // 从1开始
    }
    return b; // 返回原始值
}

🎯 最后时刻提醒清单

考前准备

  1. 打印常用模板(如果允许)
  2. 带好准考证、身份证、笔、水
  3. 穿舒适的衣服(考场可能冷)

考试中

  1. 先读所有题目,标记难度
  2. 每道题先估算最坏时间复杂度
  3. 大数组开全局,避免栈溢出
  4. 使用long long防溢出(尤其是乘法)
  5. 检查边界:n=0, n=1, n=最大值

调试技巧

// 调试宏(考完记得注释掉)
#define DEBUG
#ifdef DEBUG
    #define debug(x) cout << #x << " = " << x << endl
#else
    #define debug(x) 
#endif

// 打印数组
void printArray(int arr[], int n) {
    for(int i=0; i<n; i++) cout << arr[i] << " ";
    cout << endl;
}

时间紧急的应对策略

if (剩余时间<30分钟):
    1. 确保所有题都有代码(即使暴力)
    2. 检查输入输出格式
    3. 测试样例是否通过
    4. 注释思路争取部分分

if (遇到难题卡住):
    1. 写朴素算法(n≤10的暴力)
    2. 输出特殊情况的解
    3. 猜结论写贪心

💪 心态调整

  1. 选拔看的是相对表现,不是绝对分数
  2. 遇到难题正常,大家都难
  3. 部分分很重要,争取每一分
  4. 最后15分钟不要写新代码,专心检查

记住:4小时很长,足够你展现所有能力。先稳后快,确保简单题全对,中等题多拿分,难题有尝试。

**晚上,明天正常发挥就是胜利!加油!

posted @ 2026-02-01 21:42  酱云兔  阅读(0)  评论(0)    收藏  举报