安吉D6-T4
- 1安吉D19 T1
- 2安吉D19 T3
- 3安吉D20 模考12总结
- 4安吉D17 T3
- 5安吉D17 T4
- 6安吉D19 模考11总结
- 7安吉D15 A
- 8安吉D16 T4
- 9安吉D17 T1
- 10安吉D17 模考10总结
- 11安吉D16 T3
- 12安吉D16 模考9总结
- 13安吉D13 模考8总结
- 14安吉D13-T2
- 15安吉D13-T3
- 16安吉D12-T3
- 17安吉D11-B
- 18安吉D11-C
- 19安吉D11-D
- 20安吉D12 模考7总结
- 21安吉D12-T2
- 22安吉D11-A
- 23安吉D10 模考6总结
- 24安吉D10-T3
- 25安吉D10-T4
- 26安吉D8-E
- 27安吉D9-T4
- 28安吉D8-A
- 29安吉D8-B
- 30安吉D8-C
- 31安吉D8-D
- 32安吉D9 模考5总结
- 33安吉D9-T2
- 34安吉D9-T3
- 35安吉D6-T4本文
- 36安吉D4-G
- 37安吉D5-T2
- 38安吉D6 模考4总结
- 39安吉D6-T1
- 40安吉D6-T3
- 41安吉-开营测试 T4
- 42安吉D5 模考3总结
- 43安吉D5-T3
- 44安吉D4-A
- 45安吉D4-B
- 46安吉D4-C
- 47安吉D4:容斥原理
- 48安吉Day4-D
- 49安吉D3 模考2总结
- 50安吉D3-T1
- 51安吉D3-T2
- 52安吉D3-T4
- 53安吉D2 模考1总结
- 54安吉D2-T2
- 55安吉D1-G
- 56安吉D1-L
- 57安吉D2-T4
原题呈现
题目描述
你需要维护一个序列,初始长度为 .序列中的每个元素带有两个标记:权值 和颜色 .
本题要求强制在线.
接下来有 次操作:
1 l r x:仅关注下标在 内的元素.把其中权值不大于 的元素按原下标递增顺序取出来,形成一个子序列.对于这个子序列,若相邻两个元素颜色相同,则认为它们属于同一段.求这个子序列共有多少段.2 x y:在序列尾部插入一个新的元素,其权值为 ,颜色为 .
例如:某次查询得到的颜色序列为
那么会被分成
共 段.
输入格式
从文件 subsequence.in 中读入数据.
第一行三个整数 ,分别表示序列的初始长度、操作次数和是否强制在线.
接下来两行,第一行 个整数,表示 ;第二行 个整数,表示 (即颜色 ).
接下来 行,每行首先一个整数 ,表示本次操作的种类.
- 若 ,接下来三个整数 ,描述一次查询.
- 若 ,接下来两个整数 ,描述一次插入.
对于 ,不要求强制在线.
对于 ,要求输入的 均对上一次查询的结果按位异或,即 ,若上一次查询不存在,则 ans = 0.
解码方式示例:
int n, m, k;cin >> n >> m >> k;for (int i = 1 ; i <= m ; i++) { cin >> t; if (t == 1) { int l, r, x; cin >> l >> r >> x; l ^= ans * k; r ^= ans * k; x ^= ans * k; } else { int x, y; cin >> x >> y; x ^= ans * k; y ^= ans * k; }}输出格式
输出到文件 subsequence.out 中.
对于每个操作 ,每行输出一个整数表示查询的结果.
样例
样例输入 #1
10 10 06 8 5 9 6 10 2 4 8 92 4 3 3 4 1 2 3 3 21 7 9 71 1 4 42 2 82 1 62 10 22 10 82 8 42 3 101 5 16 51 4 14 7样例输出 #1
2055样例输入 #2
10 20 02 19 13 20 7 19 17 8 15 121 2 4 3 5 3 2 4 2 22 9 31 1 9 152 17 31 1 12 32 15 51 6 13 62 7 12 20 32 10 11 5 9 41 1 7 152 12 12 7 52 12 41 3 18 61 12 12 141 7 7 12 8 51 6 8 101 14 18 4样例输出 #2
5100300010数据范围
对于 的数据,保证:
- ;
- ;
- ;
- (解码后);
- .
子任务
本题采用捆绑测试,你必须通过子任务内所有测试点才能获得该子任务分数.
| 测试点编号 | 特殊性质 | |
|---|---|---|
| 无 | ||
| 无 | ||
| 操作 的 | ||
| 无 |
涉及到区间查询,单点修改,考虑使用树状数组或是线段树,但这道题并非简单的求区间和,于是选择使用线段树.
对于线段树的每一个节点,我们记录一个 vector,vector 中的每一个元素是一个四元组 [x, lcol, rcol, cnt],
x为选择的权值.lcol是选择权值为x时,该区间最左端有效元素的颜色.rcol是选择权值为x时,该区间最右端有效元素的颜色.cnt是选择权值为x时,该区间所有有效元素一共能组成多少段.
将这个四元组类型定义为结构体 Node.将包含 Node 的 vector 定义为类 Nodes.Nodes 即为线段树中每一个端点的数据类型.
对于该区间内每一个可能的 x,都建一个 Node,保存在该区间节点的 Nodes 中.
对于两个 Node 之间的合并,可以表示为:
- 对于
x,取 . - 对于
lcol,取 . - 对于
rcol,取 . - 对于
cnt,- 若 ,即两个节点中间部分的颜色可以合并成一大段,答案取 .
- 否则,取 .
friend Node operator+(const Node a, const Node b) { // 对于所有权值为 0 的点,我们定义它为空 (null),一个 Node 加上空还是该 Node if (a.x == 0) return b; if (b.x == 0) return a; Node ans; ans.x = max(a.x, b.x); ans.lcol = a.lcol; ans.rcol = b.rcol; ans.cnt = a.cnt + b.cnt - (a.rcol == b.lcol); return ans;}对于两个 Nodes (显然, 中的元素按照 升序排列)需要合并到 ,可以采用类似归并排序的方法.其宗旨为:
对于每一个存在于 中的四元组 ,查找 中最后一个四元组 满足 .此时 中统辖的所有元素权值是节点 所代表区间中所有小于等于 的元素.将这两个四元组合并,加入到 中,对于每一个存在于 中的四元组也是如此.
在程序中,我们使用双指针,指针 指向 ,指针 指向 .初始时, 都指向 第一个元素的前一个位置.在每一次循环时,我们需要决定主场节点:
- 检查两个指针是否指向了两个
vector的最后一个元素.- 若都指向,结束循环.
- 若指针 指向了 的最后一个元素,则主场节点为 .
- 若指针 指向了 的最后一个元素,则主场节点为 .
- 若以上情况都不满足,则比较指针 指向的下一个元素 和指针 指向的下一个元素 的大小
- 若 则主场节点为 .
- 若 则主场节点为 .
- 否则,主场节点同时为 .
- 在决定主场节点之后,将主场节点对应的指针向后移动一次.
- 最后,将 指向的元素和 指向的元素合并,加入节点 .
friend Nodes operator+(const Nodes a, const Nodes b) { Nodes ans; ans.l = a.l; ans.r = b.r; int i = -1, j = -1; while (true) { Node tmp; bool isInEndA = i + 1 >= a.size(); bool isInEndB = j + 1 >= b.size(); if (isInEndA && isInEndB) break; if (isInEndB || (!isInEndA && a[i + 1].x < b[j + 1].x)) { i++; tmp = a[i]; if (j != -1) { tmp = tmp + b[j]; } } else if (isInEndA || a[i + 1].x > b[j + 1].x) { j++; tmp = b[j]; if (i != -1) { tmp = a[i] + tmp; } } else { i++; j++; tmp = a[i] + b[j]; } ans.push_back(tmp); } return ans;}建树和修改的代码和普通线段树大差不差,因为没有区间修改,所以都不需要 pushup.
由于最多会有 次插入操作,所以要预留出这些空间.线段树的大小要开 .
void build(int t, int l, int r) { f[t].l = l, f[t].r = r; if (l == r) { if (l > n) { // 大于 n 的点当前还是空的 return; } Node nd; nd.x = a[l]; nd.lcol = b[l]; nd.rcol = b[l]; nd.cnt = 1; f[t].push_back(nd); return; } int mid = (l + r) / 2; build(t * 2, l, mid); build(t * 2 + 1, mid + 1, r); pushup(t);}void pushup(int t) { f[t] = f[t * 2] + f[t * 2 + 1]; }void Modify(int t, int pos, int a, int b) { if (f[t].l == pos && f[t].r == pos) { f[t].push_back({a, b, b, 1}); return; } if (f[t].l > pos || f[t].r < pos) { return; } Modify(t * 2, pos, a, b); Modify(t * 2 + 1, pos, a, b);
pushup(t);}至于查询操作,需要注意:
- 若当前区间被查询区间完全覆盖,则需要使用
upper_bound找到当前Nodes中所有的Node,满足权值 小于等于查询权值 ,其中权值最大的一个.将这个节点返回. - 若当前区间和查询区间完全没有交集,则返回一个空的
Node(在本程序中,权值为 0 的Node为空的Node,权值默认为 0),表示什么都没有.
Node Query(int t, int x, int y, int tgt) { if (f[t].l >= x && f[t].r <= y) { auto it = upper_bound(f[t].begin(), f[t].end(), tgt); if (it == f[t].begin()) return Node(); return *(it - 1); } if (f[t].l > y || f[t].r < x) { return Node(); } return Query(t * 2, x, y, tgt) + Query(t * 2 + 1, x, y, tgt);}这样线段树就建完了.
在处理查询时,调用线段树的 Query 方法,收到节点中的 cnt 即为答案.
在处理插入时,直接调用线段树的 Modify 方法即可.
但是这样每次插入一个节点,每一层都会调用一次 pushup,pushup 复杂度最高 ,每一次查询的复杂度为 . 次查询的复杂度为 ,不如暴力.
仔细想想,对于一个区间,只有其中的全部元素都被插入之后,这个区间才会被访问到.因此,只需要在 的时候执行一次 pushup 即可.
void Modify(int t, int pos, int a, int b) { if (f[t].l == pos && f[t].r == pos) { f[t].push_back({a, b, b, 1}); return; } if (f[t].l > pos || f[t].r < pos) { return; } Modify(t * 2, pos, a, b); Modify(t * 2 + 1, pos, a, b);
if (f[t].r == pos) { pushup(t); }}标程
#include <bits/stdc++.h>using namespace std;const int N = 3e5 + 100;const int INF = 0x3f3f3f3f;int n, m, k, a[2 * N], b[2 * N];int l, r, x, y, ans;
struct SegmentTree { struct Node { int x = 0, lcol = 0, rcol = 0, cnt = 0; friend Node operator+(const Node a, const Node b) { if (a.x == 0) return b; if (b.x == 0) return a; Node ans; ans.x = max(a.x, b.x); ans.lcol = a.lcol; ans.rcol = b.rcol; ans.cnt = a.cnt + b.cnt - (a.rcol == b.lcol); return ans; } friend bool operator<(const int a, const Node b) { return a < b.x; } }; class Nodes : public vector<Node> { public: int l, r; friend Nodes operator+(const Nodes a, const Nodes b) { Nodes ans; ans.l = a.l; ans.r = b.r; int i = -1, j = -1; while (true) { Node tmp; bool isInEndA = i + 1 >= a.size(); bool isInEndB = j + 1 >= b.size(); if (isInEndA && isInEndB) break; if (isInEndB || (!isInEndA && a[i + 1].x < b[j + 1].x)) { i++; tmp = a[i]; if (j != -1) { tmp = tmp + b[j]; } } else if (isInEndA || a[i + 1].x > b[j + 1].x) { j++; tmp = b[j]; if (i != -1) { tmp = a[i] + tmp; } } else { i++; j++; tmp = a[i] + b[j]; } ans.push_back(tmp); } return ans; } } f[N * 8]; void build(int t, int l, int r) { f[t].l = l, f[t].r = r; if (l == r) { if (l > n) { return; } Node nd; nd.x = a[l]; nd.lcol = b[l]; nd.rcol = b[l]; nd.cnt = 1; f[t].push_back(nd); return; } int mid = (l + r) / 2; build(t * 2, l, mid); build(t * 2 + 1, mid + 1, r); pushup(t); } void pushup(int t) { f[t] = f[t * 2] + f[t * 2 + 1]; } void Modify(int t, int pos, int a, int b) { if (f[t].l == pos && f[t].r == pos) { f[t].push_back({a, b, b, 1}); return; } if (f[t].l > pos || f[t].r < pos) { return; } Modify(t * 2, pos, a, b); Modify(t * 2 + 1, pos, a, b);
if (f[t].r == pos) { pushup(t); } } Node Query(int t, int x, int y, int tgt) { if (f[t].l >= x && f[t].r <= y) { auto it = upper_bound(f[t].begin(), f[t].end(), tgt); if (it == f[t].begin()) return Node(); return *(it - 1); } if (f[t].l > y || f[t].r < x) { return Node(); } return Query(t * 2, x, y, tgt) + Query(t * 2 + 1, x, y, tgt); }} sgTree;
signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> m >> k; for (int i = 1; i <= n; i++) { cin >> a[i]; } for (int i = 1; i <= n; i++) { cin >> b[i]; } sgTree.build(1, 1, n + m); int cnt = n; for (int i = 1; i <= m; i++) { int t; cin >> t; if (t == 1) { cin >> l >> r >> x; l ^= ans * k; r ^= ans * k; x ^= ans * k; int nds = sgTree.Query(1, l, r, x).cnt; ans = nds; cout << ans << endl; } else { cin >> x >> y; x ^= ans * k; y ^= ans * k; sgTree.Modify(1, ++cnt, x, y); } }}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


