章节概述

下标技巧是算法竞赛中最基础也最灵活的技巧之一,涉及数组下标的映射、转换、偏移和压缩,
直接影响程序的正确性和效率。

核心原理

1. 0-based vs 1-based

场景推荐原因
前缀和、差分、DP1-basedpre[0]=0 自然成立,避免 i-1<0 边界判断
原始输入、STL0-basedSTL 统一使用 0-based
混合使用注意转换a_1based[i] = input[i-1]

2. 离散化 (坐标压缩)

当数据范围很大(如 10^9)但实际使用的值很少(如 10^5),将原值映射到紧凑范围 [1, m]:

步骤: 排序 → 去重 → 二分查找映射
m = unique(tmp, tmp + n) - tmp
a[i] = lower_bound(tmp, tmp + m, a[i]) - tmp + 1

保序映射,时间复杂度 O(n log n)。

3. 负数下标处理

const int OFFSET = 1005;
int cnt[2010];
int& at(int idx) { return cnt[idx + OFFSET]; }
// 将 [-1000, 1000] 映射到 [5, 2005]

关键数据结构


下标偏移

在动态规划和前缀和问题中,常用 1-based 下标避免边界判断。

// 1-based: 自然避免了 i-1=0 的越界问题
int dp[1005][1005] = {0};
for (int i = 1; i <= n; i++)
    for (int j = 1; j <= m; j++)
        dp[i][j] = dp[i-1][j] + dp[i][j-1] + a[i][j];

下标映射 (离散化)

#include <iostream>
#include <algorithm>
using namespace std;
int a[100005], tmp[100005];
 
int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        tmp[i] = a[i];
    }
    sort(tmp, tmp + n);
    int m = unique(tmp, tmp + n) - tmp;
    for (int i = 0; i < n; i++)
        a[i] = lower_bound(tmp, tmp + m, a[i]) - tmp + 1;
    return 0;
}

P1047 [NOIP2005 普及组] 校门外的树

题目: 长度为 L 的马路上有树,挖掉 M 个区间的树,求剩余数量。

#include <iostream>
using namespace std;
bool tree[10005];
 
int main() {
    int L, M;
    cin >> L >> M;
    for (int i = 0; i <= L; i++) tree[i] = true;
    while (M--) {
        int u, v;
        cin >> u >> v;
        for (int i = u; i <= v; i++)
            tree[i] = false;
    }
    int cnt = 0;
    for (int i = 0; i <= L; i++)
        if (tree[i]) cnt++;
    cout << cnt << endl;
    return 0;
}

P1957 口算练习题

题目: 给出一系列算式,判断是加法还是减法运算,输出竖式格式。

#include <iostream>
#include <string>
#include <cstring>
using namespace std;
 
int main() {
    int n;
    cin >> n;
    while (n--) {
        string s, a, b;
        char op;
        cin >> s;
        if (s[0] >= 'a' && s[0] <= 'z') {
            op = s[0];
            cin >> a >> b;
        } else {
            a = s;
            cin >> b;
        }
        int len = max(a.size(), b.size()) + 1;
        cout << a << endl;
        cout << b << endl;
        for (int i = 0; i < len; i++) cout << '-';
        cout << endl;
    }
    return 0;
}

推荐练习题(洛谷)


相关技巧


  • 前缀和: 下标偏移在 1-based 前缀和中是标准做法
  • 差分: 同样依赖 1-based 下标 + 偏移量
  • 数组: 数组下标的基本操作
  • 二分查找: 离散化常配合二分/STL 实现坐标压缩
  • 排序: 离散化的第一步是排序

多平台练习

| 洛谷 | 本题单 | 竞赛基础 |
| POJ (北大) | PKU JudgeOnline | 经典题目,适合巩固 |
| HDU (杭电) | HDU OJ | 暑期多校训练 |
| Codeforces | Codeforces | 国际竞赛,适合提升 |