1. 项目背景与题目解析
最近在准备信息学奥赛的同学肯定对P5627和P5751这两道经典题目不陌生。作为NOI1999年的老题,它们至今仍是检验选手对01串处理能力的标杆。这两道题看似简单,实则暗藏玄机,考察了选手对字符串处理、位运算和算法优化的综合掌握程度。
我当年第一次做这两题时,就被它们精巧的设计所折服。P5627主要考察基础字符串操作,而P5751则需要更深入的位运算技巧。下面我就结合自己刷题的经验,详细讲解这两道题的解题思路和C++实现方法。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 题目P5627详解
2.1 题目要求分析
P5627题目大意是给定一个01字符串,要求进行一系列操作后输出最终结果。具体操作包括:
- 翻转指定区间的字符(0变1,1变0)
- 查询指定区间内1的个数
- 将整个字符串循环左移k位
2.2 数据结构选择
对于这类区间操作问题,线段树是最合适的数据结构。它能以O(logn)的时间复杂度完成区间更新和查询。考虑到题目中的翻转操作,我们需要在线段树节点中维护:
- 区间内1的个数
- 翻转标记(lazy tag)
cpp复制struct Node {
int l, r;
int cnt; // 1的个数
bool flip; // 翻转标记
} tree[MAXN * 4];
2.3 核心算法实现
2.3.1 建树
首先我们需要根据初始字符串建立线段树:
cpp复制void build(int p, int l, int r, const string &s) {
tree[p].l = l;
tree[p].r = r;
tree[p].flip = false;
if (l == r) {
tree[p].cnt = (s[l] == '1');
return;
}
int mid = (l + r) / 2;
build(p*2, l, mid, s);
build(p*2+1, mid+1, r, s);
tree[p].cnt = tree[p*2].cnt + tree[p*2+1].cnt;
}
2.3.2 区间翻转
翻转操作需要处理懒标记:
cpp复制void push_down(int p) {
if (tree[p].flip) {
tree[p*2].cnt = (tree[p*2].r - tree[p*2].l + 1) - tree[p*2].cnt;
tree[p*2+1].cnt = (tree[p*2+1].r - tree[p*2+1].l + 1) - tree[p*2+1].cnt;
tree[p*2].flip = !tree[p*2].flip;
tree[p*2+1].flip = !tree[p*2+1].flip;
tree[p].flip = false;
}
}
void flip_range(int p, int l, int r) {
if (tree[p].l >= l && tree[p].r <= r) {
tree[p].cnt = (tree[p].r - tree[p].l + 1) - tree[p].cnt;
tree[p].flip = !tree[p].flip;
return;
}
push_down(p);
int mid = (tree[p].l + tree[p].r) / 2;
if (l <= mid) flip_range(p*2, l, r);
if (r > mid) flip_range(p*2+1, l, r);
tree[p].cnt = tree[p*2].cnt + tree[p*2+1].cnt;
}
2.3.3 循环左移处理
循环左移k位可以通过三次
