视频加载失败

安吉D6-T4

2885 字
14 分钟
安吉D6-T4
原题呈现

题目描述#

你需要维护一个序列,初始长度为 nn.序列中的每个元素带有两个标记:权值 aia_i 和颜色 cic_i.

本题要求强制在线.

接下来有 mm 次操作:

  • 1 l r x:仅关注下标在 [l,r][l, r] 内的元素.把其中权值不大于 xx 的元素按原下标递增顺序取出来,形成一个子序列.对于这个子序列,若相邻两个元素颜色相同,则认为它们属于同一段.求这个子序列共有多少段.
  • 2 x y:在序列尾部插入一个新的元素,其权值为 xx,颜色为 yy.

例如:某次查询得到的颜色序列为

[1,4,4,6,6,6,2,5,5][1,4,4,6,6,6,2,5,5]

那么会被分成

[1],[4,4],[6,6,6],[2],[5,5][1], [4,4], [6,6,6], [2], [5,5]

共 55 段.

输入格式#

从文件 subsequence.in 中读入数据.

第一行三个整数 n,m,kn, m, k,分别表示序列的初始长度、操作次数和是否强制在线.

接下来两行,第一行 nn 个整数,表示 aia_i;第二行 nn 个整数,表示 bib_i(即颜色 cic_i).

接下来 mm 行,每行首先一个整数 tt,表示本次操作的种类.

  • 若 t=1t=1,接下来三个整数 l,r,xl, r, x,描述一次查询.
  • 若 t=2t=2,接下来两个整数 x,yx, y,描述一次插入.

对于 k=0k=0,不要求强制在线. 对于 k=1k=1,要求输入的 l,r,x,yl, r, x, y 均对上一次查询的结果按位异或,即 l′=l⊕ansl' = l \oplus ans,若上一次查询不存在,则 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 中.

对于每个操作 11,每行输出一个整数表示查询的结果.

样例#

样例输入 #1#

10 10 0
6 8 5 9 6 10 2 4 8 9
2 4 3 3 4 1 2 3 3 2
1 7 9 7
1 1 4 4
2 2 8
2 1 6
2 10 2
2 10 8
2 8 4
2 3 10
1 5 16 5
1 4 14 7

样例输出 #1#

2
0
5
5

样例输入 #2#

10 20 0
2 19 13 20 7 19 17 8 15 12
1 2 4 3 5 3 2 4 2 2
2 9 3
1 1 9 15
2 17 3
1 1 12 3
2 15 5
1 6 13 6
2 7 1
2 20 3
2 10 1
1 5 9 4
1 1 7 15
2 12 1
2 7 5
2 12 4
1 3 18 6
1 12 12 14
1 7 7 1
2 8 5
1 6 8 10
1 14 18 4

样例输出 #2#

5
1
0
0
3
0
0
0
1
0

数据范围#

对于 100%100\% 的数据,保证:

  • 1≤n,m≤3×1051 \le n, m \le 3 \times 10^5;
  • 0≤k≤10 \le k \le 1;
  • 1≤op≤21 \le op \le 2;
  • 1≤l,r≤n1 \le l, r \le n(解码后);
  • 1≤ai,ci,x,y≤1091 \le a_i, c_i, x, y \le 10^9.

子任务#

本题采用捆绑测试,你必须通过子任务内所有测试点才能获得该子任务分数.

测试点编号n,m≤n, m \le特殊性质
1∼101 \sim 1010310^3无
11∼2011 \sim 2010510^5无
21∼3021 \sim 303×1053\times10^5k=0k=0
31∼4031 \sim 403×1053\times10^5操作 11 的 l=1l=1
41∼5041 \sim 503×1053\times10^5无

涉及到区间查询,单点修改,考虑使用树状数组或是线段树,但这道题并非简单的求区间和,于是选择使用线段树.

对于线段树的每一个节点,我们记录一个 vector,vector 中的每一个元素是一个四元组 [x, lcol, rcol, cnt],

  • x 为选择的权值.
  • lcol 是选择权值为 x 时,该区间最左端有效元素的颜色.
  • rcol 是选择权值为 x 时,该区间最右端有效元素的颜色.
  • cnt 是选择权值为 x 时,该区间所有有效元素一共能组成多少段.

将这个四元组类型定义为结构体 Node.将包含 Node 的 vector 定义为类 Nodes.Nodes 即为线段树中每一个端点的数据类型.

对于该区间内每一个可能的 x,都建一个 Node,保存在该区间节点的 Nodes 中.

对于两个 Node a,ba,b 之间的合并,可以表示为:

  • 对于 x,取 max⁡{a.x,b.x}\max\{a.x,b.x\}.
  • 对于 lcol,取 a.lcola.lcol.
  • 对于 rcol,取 b.rcolb.rcol.
  • 对于 cnt,
    • 若 a.rcol=b.lcola.rcol = b.lcol,即两个节点中间部分的颜色可以合并成一大段,答案取 a.cnt+b.cnt−1a.cnt + b.cnt-1.
    • 否则,取 a.cnt+b.cnta.cnt + b.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 m,nm,n(显然,m,nm,n 中的元素按照 .x.x 升序排列)需要合并到 kk,可以采用类似归并排序的方法.其宗旨为:

对于每一个存在于 mm 中的四元组 ii,查找 nn 中最后一个四元组 jj 满足 i.x≥j.xi.x \geq j.x.此时 jj 中统辖的所有元素权值是节点 nn 所代表区间中所有小于等于 i.xi.x 的元素.将这两个四元组合并,加入到 kk 中,对于每一个存在于 nn 中的四元组也是如此.

在程序中,我们使用双指针,指针 ii 指向 mm,指针 jj 指向 nn.初始时,i,ji,j 都指向 m,nm,n 第一个元素的前一个位置.在每一次循环时,我们需要决定主场节点:

  • 检查两个指针是否指向了两个 vector 的最后一个元素.
    • 若都指向,结束循环.
    • 若指针 ii 指向了 mm 的最后一个元素,则主场节点为 nn.
    • 若指针 jj 指向了 nn 的最后一个元素,则主场节点为 mm.
    • 若以上情况都不满足,则比较指针 ii 指向的下一个元素 pp 和指针 jj 指向的下一个元素 qq 的大小
      • 若 p<qp<q 则主场节点为 nn.
      • 若 p>qp>q 则主场节点为 mm.
      • 否则,主场节点同时为 m,nm,n.
  • 在决定主场节点之后,将主场节点对应的指针向后移动一次.
  • 最后,将 ii 指向的元素和 jj 指向的元素合并,加入节点 kk.
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.

由于最多会有 mm 次插入操作,所以要预留出这些空间.线段树的大小要开 m+nm+n.

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,满足权值 xx 小于等于查询权值 qxqx,其中权值最大的一个.将这个节点返回.
  • 若当前区间和查询区间完全没有交集,则返回一个空的 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 复杂度最高 O(n)O(n),每一次查询的复杂度为 O(nlog⁡n)O(n\log n).qq 次查询的复杂度为 O(nqlog⁡n)O(nq\log n),不如暴力.

仔细想想,对于一个区间,只有其中的全部元素都被插入之后,这个区间才会被访问到.因此,只需要在 pos=rpos=r 的时候执行一次 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);
}
}
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D6-T4
https://blog.jerrylab.top/posts/problem/anji2026/D6/T4/
作者
Jerry
发布于
2026-08-07
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0