上帝造题的七分钟 2 / 花神游历各国 - 洛谷
题目背景
XLk 觉得《上帝造题的七分钟》不太过瘾,于是有了第二部。
题目描述
"第一分钟,X 说,要有数列,于是便给定了一个正整数数列。
第二分钟,L 说,要能修改,于是便有了对一段数中每个数都开平方(下取整)的操作。
第三分钟,k 说,要能查询,于是便有了求一段数的和的操作。
第四分钟,彩虹喵说,要是 noip 难度,于是便有了数据范围。
第五分钟,诗人说,要有韵律,于是便有了时间限制和内存限制。
第六分钟,和雪说,要省点事,于是便有了保证运算过程中及最终结果均不超过 $$6$$ 位有符号整数类型的表示范围的限制。
第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。"
——《上帝造题的七分钟·第二部》
所以这个神圣的任务就交给你了。
输入格式
第一行一个整数 $n$,代表数列中数的个数。
第二行 $$$$ 个正整数,表示初始状态下数列中的数。
第三行一个整数 $m$,表示有 $$$$ 次操作。
接下来 $$$$ 行每行三个整数
k l r
。
$$k=$$ 表示给 $$[l,r$$ 中的每个数开平方(下取整)。
$$k=$$ 表示询问 $$[l,r$$ 中各个数的和。
数据中有可能 **$l>r$****,所以遇到这种情况请交换 ****$l$**** 和 ****$r$**。
输出格式
对于询问操作,每行输出一个回答。
样例 #1
样例输入 #1
10 1 2 3 4 5 6 7 8 9 10 5 0 1 10 1 1 10 1 1 5 0 5 8 1 4 8
样例输出 #1
19 7 6
提示
对于 $$30\$$ 的数据,$1\le n,m\le 10^3$,数列中的数不超过 $32767$。
对于 $$100\$$ 的数据,$1\le n,m\le 10^5$,$1\le l,r\le n$,数列中的数大于 $0$,且不超过 $10^{12}$。
1.
范围查询操作,查询 L~R 范围中 sum 和。
首先对于 sum 信息,可以用 O(1)的时间,从子范围的信息加工出父亲范围的信息。因此可以作为线段树的查询信息。
2.
范围开根号 sqrt 操作,对 L~R 范围中所有元素进行 sqrt 操作。
首先对于 sqrt 操作,不能用 O(1)的时间就把这段范围维护的信息加工出来。
所以没办法在线段树中用懒更新去使用这个操作,只能暴力递归去维护所有的有关节点。
因此时间复杂度没办法达到 O(logN)。
3.
但是因为我们是 sqrt 操作,对于一个数是 1,sqrt 操作就可以不用执行,也就是如果 L~R 区间最大值是 1,说明全部元素都是 1,sqrt 操作就可以不执行,这是一种剪枝。
而每个节点 sqrt 操作的次数是有限的,平均下来的时间复杂度也不会很高。
因此不使用懒更新操作,实现这个操作时间复杂度也不会很大。
4.
因此我们维护的信息是区间和 sum,区间最大值 max_value。
并没有需要维护的操作。
sqrt 操作是自己暴力递归维护信息。所以不需要在 treenode 中存储对应信息。
5.
没有懒更新操作就没有 pushdown 操作。
6.
mysqrt 递归函数,用于执行某个任务,在这个任务的前提下维护所有需要维护的节点的信息。
任务是 L~R 全部开根号。
对于当前节点,对应的区间是 l~r,如果不与任务区间重合,不需要维护信息,直接返回 return。
如果如果完全重叠,需要看该区间最大值是不是 1,如果是 1 就不用维护信息,直接返回 return。
如果不是 1,那必须继续递归,维护左孩子,维护右孩子,然后用维护左孩子右孩子的值维护自己。
如果重叠一部分,维护左孩子,维护右孩子,然后维护自己。
7.
还有一个很恶心的地方,题目的下标是从 1 开始的,1-based,所以如果要使用 0-based 需要转化。
#include<bits/stdc++.h> // 引入常用的库文件
using namespace std;
using LL = long long; // 定义长整型别名LL,用于处理大数
class SegmentTree { // 定义线段树类
private:
LL size; // 数列的大小+1
vector<LL> arr; // 储存数列的数组
struct treenode { // 定义线段树节点结构体
LL sum; // 存储区间和
LL max_value; // 存储区间最大值
};
vector<treenode> t; // 储存线段树的数组
void pushup(LL i) { // 向上更新节点值
t[i].max_value = max(t[i << 1].max_value, t[i << 1 | 1].max_value); // 更新最大值
t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum; // 更新区间和
}
void build(LL l, LL r, LL rt) { // 构建线段树
if (l == r) { // 叶节点
t[rt].sum = t[rt].max_value = arr[l]; // 叶节点的值初始化
return;
}
LL mid = (l + r) >> 1; // 中点
build(l, mid, rt << 1); // 构建左子树
build(mid + 1, r, rt << 1 | 1); // 构建右子树
pushup(rt); // 更新当前节点
}
LL _query(LL L, LL R, LL l, LL r, LL rt) { // 区间查询
if (r < L || R < l) return 0; // 查询区间与当前区间无交集
if (L <= l && r <= R) { // 当前区间完全包含在查询区间内
return t[rt].sum; // 返回当前区间的和
}
LL mid = (l + r) >> 1; // 中点
// 查询左右子树,并返回结果之和
return _query(L, R, l, mid, rt << 1) +
_query(L, R, mid + 1, r, rt << 1 | 1);
}
void _mysqrt(LL L, LL R, LL l, LL r, LL rt) { // 区间平方根
if (r < L || R < l) return; // 操作区间与当前区间无交集
if (t[rt].max_value <= 1) return; // 区间内的最大值小于等于1,无需操作
if (l == r) { // 叶节点
t[rt].sum = sqrt(t[rt].sum); // 对叶节点值开平方
t[rt].max_value = t[rt].sum; // 更新最大值
return;
}
LL mid = (l + r) >> 1; // 中点
_mysqrt(L, R, l, mid, rt << 1); // 操作左子树
_mysqrt(L, R, mid + 1, r, rt << 1 | 1); // 操作右子树
pushup(rt); // 更新当前节点
}
public:
void mysqrt(LL L, LL R) { // 对外的平方根操作接口
L++, R++; // 转换为1-based索引
_mysqrt(L, R, 1, size - 1, 1); // 调用私有方法
}
LL query(LL L, LL R) { // 对外的查询接口
L++, R++; // 转换为1-based索引
return _query(L, R, 1, size - 1, 1); // 调用私有方法
}
SegmentTree(vector<LL>& nums) { // 构造函数
size = nums.size() + 1; // 设置size
t.resize(size << 2); // 分配线段树空间
arr.resize(size); // 分配数列空间
for (int i = 0; i < nums.size(); i++) { // 初始化数列
arr[i + 1] = nums[i];
}
build(1, size - 1, 1); // 构建线段树
}
};
int main() {
LL n; cin >> n; // 读入数列的长度
vector<LL> nums; // 定义存储数列的向量
for (LL i = 1; i <= n; i++) { // 循环读入数列元素
LL tmp; cin >> tmp; // 读入数列中的每个数
nums.push_back(tmp); // 将数加入向量
}
SegmentTree t(nums); // 创建线段树对象
LL k; cin >> k; // 读入操作的次数
for (LL i = 0; i < k; i++) { // 对每个操作进行处理
LL op, left, right; // 定义操作类型和操作区间的左右端点
cin >> op >> left >> right; // 读入操作数据
left--, right--; // 转换为0-based索引
if (left > right) swap(left, right); // 保证left小于等于right
if (op == 0) { // 如果操作为开平方
t.mysqrt(left, right); // 对指定区间的元素执行平方根操作
} else { // 如果操作为查询和
cout << t.query(left, right) << endl; // 输出查询到的区间和
}
}
return 0; // 程序结束
}
The Child and Sequence - 洛谷
题意翻译
题目描述
有一个长度为 nn 的数列 \{a_n\}{an} 和 mm 次操作,操作内容如下:
格式为
1 l r
,表示求 \sum \limits _{i=l}^{r} a_ii=l∑rai 的值并输出。格式为
2 l r x
,表示对区间 [l,r][l,r] 内每个数取模,模数为 xx。格式为
3 k x
,表示将 a_kak 修改为 xx。1 \le n,m \le 10^51≤n,m≤105, 1\le l,r,k,x \le 10^91≤l,r,k,x≤109.
输入格式
第一行两个正整数 n,mn,m,分别表示数列长度和操作次数。
第二行给出长为 nn 的数列 \{a_n\}{an}。
接下来 mm 行,每行表示一次操作。
输出格式
对于每个操作 11,输出答案,每行一个整数。答案可能大于 2^{31}-1231−1。
输入输出样例
输入 #1 复制
5 5
1 2 3 4 5
2 3 5 4
3 3 5
1 2 5
2 1 3 3
1 1 3
输出 #1 复制
8
5
输入 #2 复制
10 10
6 9 6 7 6 1 10 10 9 5
1 3 9
2 7 10 9
2 5 10 8
1 4 7
3 3 7
2 7 9 9
1 2 4
1 6 6
1 5 9
3 1 10
输出 #2 复制
49
15
23
1
9
1.
我们需要维护的信息,首选对于查询信息。
查询区间和 sum,对于查询信息,是否可以用 O(1)的时间用左右孩子的信息加工维护出自己的信息,显然是可以的。
2.
对于更新操作,维护信息。
对于取模的操作,是否可以用 O(1)的时间,直接维护出当前节点的信息?显然是不能的,我需要把 L~R 区间上所有的元素取模,我能否直接得出 L~R 区间和 sum 的信息?不能,必须调研 L~R 每一个被取模的操作结果之后才能得到 sum 信息。
因此只能暴力递归,对于暴力递归希望能够有剪枝方法去减少时间复杂度。
3.
一个 a 对某一个 b 取模,可以认为取模的结果是 a/2。
因此对于每一个节点取模的操作是有限的,如果 L~R 区间的最大值小于 C,C 是任务,对 L~R 区间所有元素对 C 取模。
如果最大值小于 C,此时不需要操作。
因此剪枝的操作需要维护区间最大值的信息。
区间最大值是查询信息,能否用 O(1)的时间从左右孩子信息加工维护自己的信息,显然是可以的,L~R 区间的最大值,等于 L~mid 区间最大值,mid+1~R 区间最大值,两者之间的最大值。
4.
单点更新操作,因为是单点更新,所以必须访问叶节点,对于这种操作没办法由某一个树根节点信息直接得到,所以不考虑在 treenode 中添加对于的维护操作信息。
直接暴力递归维护查询信息即可。
5.
对于 treenode 没有懒更新对应的操作,因此没有 pushdown 操作。
#include<bits/stdc++.h> // 引入所有标准库
using namespace std;
using LL = long long; // 定义一个长整型的别名LL
class SegmentTree{ // 定义一个线段树的类
private:
LL size; // 存储线段树的大小
vector<LL> arr; // 原始数组
struct treenode { // 线段树节点的结构
LL sum; // 该节点区间的和
LL max_value; // 该节点区间的最大值
};
vector<treenode> t; // 线段树的数组表示
void pushup(LL i) { // 更新节点信息,从子节点更新到父节点
t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum; // 父节点的和等于两个子节点的和
t[i].max_value = max(t[i << 1].max_value, t[i << 1 | 1].max_value); // 父节点的最大值等于两个子节点最大值的较大者
}
void build(LL l, LL r, LL rt) { // 构建线段树的函数
if (l == r) { // 如果当前区间只有一个元素
t[rt].sum = t[rt].max_value = arr[l]; // 则直接将该元素的值赋给sum和max_value
return;
}
LL mid = (l + r) >> 1; // 分割当前区间
build(l, mid, rt << 1); // 递归构建左子树
build(mid + 1, r, rt << 1 | 1); // 递归构建右子树
pushup(rt); // 更新当前节点信息
}
LL _query(LL L, LL R, LL l, LL r, LL rt) { // 区间查询函数
if (r < L || R < l) return 0; // 如果查询区间与当前区间不重叠,返回0
if (L <= l && r <= R) { // 如果当前区间完全包含在查询区间内
return t[rt].sum; // 返回当前节点的和
}
LL mid = (l + r) >> 1; // 分割当前区间
return _query(L, R, l, mid, rt << 1) + _query(L, R, mid + 1, r, rt << 1 | 1); // 查询左右子树并返回结果的和
}
void _mymod(LL L, LL R, LL C, LL l, LL r, LL rt) { // 区间取模函数
if (C > t[rt].max_value) return; // 如果区间最大值小于模数,不需要操作
if (r < L || R < l) return; // 如果操作区间与当前区间不重叠,直接返回
if (l == r) { // 如果当前区间只有一个元素
t[rt].sum = t[rt].sum % C; // 对该元素取模
t[rt].max_value = t[rt].sum; // 更新最大值
return;
}
LL mid = (l + r) >> 1; // 分割当前区间
_mymod(L, R, C, l, mid, rt << 1); // 对左子树进行取模操作
_mymod(L, R, C, mid + 1, r, rt << 1 | 1); // 对右子树进行取模操作
pushup(rt); // 更新当前节点信息
}
void _update(LL index, LL C, LL l, LL r, LL rt) { // 单点更新函数
if (r < index || index < l) return; // 如果要更新的索引不在当前区间内,直接返回
if (l == r && index == l) { // 如果找到了要更新的位置
t[rt].max_value = t[rt].sum = C; // 更新该位置的值
return;
}
LL mid = (l + r) >> 1; // 分割当前区间
_update(index, C, l, mid, rt << 1); // 更新左子树
_update(index, C, mid + 1, r, rt << 1 | 1); // 更新右子树
pushup(rt); // 更新当前节点,确保节点的信息是最新的
}
public:
SegmentTree(vector<LL>& nums) { // 构造函数,初始化线段树
size = nums.size() + 1; // 数组大小,从1开始索引
arr.resize(size); // 调整原数组大小
t.resize(size << 2); // 分配线段树所需的空间大小
for (LL i = 0; i < nums.size(); i++) { // 将原始数据复制到arr中,从1开始索引
arr[i + 1] = nums[i];
}
build(1, size - 1, 1); // 构建线段树
}
void update(LL index, LL c) { // 对外的单点更新接口
_update(index, c, 1, size - 1, 1); // 调用内部的单点更新方法
}
void mymod(LL L, LL R, LL C) { // 对外的区间取模接口
_mymod(L, R, C, 1, size - 1, 1); // 调用内部的区间取模方法
}
LL query(LL L, LL R) { // 对外的区间查询接口
return _query(L, R, 1, size - 1, 1); // 调用内部的区间查询方法
}
};
int main() { // 主函数
LL n, m; // 定义数列长度和操作次数
cin >> n >> m; // 输入数列长度和操作次数
vector<LL> nums(n); // 定义数列
for (LL i = 0; i < nums.size(); i++) { // 循环读入数列元素
LL tmp;
cin >> tmp; // 读入数列中的每个数
nums[i] = tmp; // 存储到数列中
}
SegmentTree t(nums); // 创建一个线段树实例
for (LL i = 1; i <= m; i++) { // 处理每个操作
LL op; cin >> op; // 读入操作类型
if (op == 1) { // 如果操作类型为1,进行区间查询
LL left, right;
cin >> left >> right; // 读入左右边界
cout << t.query(left, right) << endl; // 输出查询结果
} else if (op == 2) { // 如果操作类型为2,进行区间取模操作
LL left, right, c;
cin >> left >> right >> c; // 读入左右边界和模数
t.mymod(left, right, c); // 进行取模操作
} else { // 如果操作类型为3,进行单点更新操作
LL index, c;
cin >> index >> c; // 读入索引和新的值
t.update(index, c); // 更新指定位置的值
}
}
}
[HAOI2014] 贴海报 - 洛谷
题目描述
Bytetown 城市要进行市长竞选,所有的选民可以畅所欲言地对竞选市长的候选人发表言论。为了统一管理,城市委员会为选民准备了一个张贴海报的 electoral 墙。
张贴规则如下:
electoral 墙是一个长度为 $$$$ 个单位的长方形,每个单位记为一个格子;
所有张贴的海报的高度必须与 electoral 墙的高度一致的;
每张海报以
A B
表示,即从第 $$$$ 个格子到第 $$$$ 个格子张贴海报;
后贴的海报可以覆盖前面已贴的海报或部分海报。
现在请你判断,张贴完所有海报后,在 electoral 墙上还可以看见多少张海报。
输入格式
第一行,两个正整数 $N,M$,分别表示 electoral 墙的长度和海报个数。
接下来 $$$$ 行,每行两个正整数 $A_i,B_i$,表示每张海报张贴的位置。
输出格式
输出贴完所有海报后,在 electoral 墙上还可以看见的海报数。
样例 #1
样例输入 #1
100 5 1 4 2 6 8 10 3 4 7 10
样例输出 #1
4
提示
!
约束条件
$$10\le N \le 10000000,1\le M\le 1000,1\le A_i \le B_i \le 1000000$$
所有的数据都是正整数,数据之间有一个空格。
离散化+暴力求解
1.
离散化,对于每一个点下标,映射到 1,2,3,4.....
但是离散化操作和常规的操作有一点不一样。
对于每一个点需要把后面一个下标也添加映射中。这一点很重要。
2.
离散化之后,用 kind 表示每一种海报,也就是划分集合,kind=1 表示一种海报,一个集合,kind=2 表示一种海报,一个集合,以此类推。
3.
用 count 表示墙,所有值初始化为 -1,表示没有海报,对于每一种海报,把对应区间设置为某一个集合表示某一种海报。
这个操作对应区间修改。
4.
最后筛选海报,kind 表示了海报种类的上界,此时设置 visit 统计海报种类是否出现。
ret 计数海报出现的次数即可。
#include<bits/stdc++.h> // 包含所有标准库
using namespace std;
int main() {
int n, m; // n表示墙的长度,m表示海报个数
cin >> n >> m; // 读入墙的长度和海报个数
map<int, int> point_index; // 使用map来压缩坐标,将连续的坐标映射到连续的整数
vector<pair<int, int>> point(m); // 存储每个海报的左右边界
for (int i = 1; i <= m; i++) { // 遍历每个海报
int left; int right;
cin >> left >> right; // 读入每个海报的左右边界
if (left > right) continue; // 如果左边界大于右边界,跳过
right = min(n, right); // 确保右边界不超过墙的长度
point[i - 1].first = left; // 存储左边界
point[i - 1].second = right; // 存储右边界
// 为每个点及其邻近点在map中创建映射
point_index[left];
point_index[left+1];
point_index[right];
point_index[right+1];
}
int index = 1; // 用于给每个不同的坐标点分配一个唯一的索引
for (auto& x : point_index) { // 遍历所有的坐标点
x.second = index++; // 给每个坐标点分配索引
}
int kind = 1; // 用于标记每个海报的唯一标识
vector<int> count(point_index.size()+1,-1); // 创建一个数组用来标记每个压缩后的位置上贴的是哪个海报
for (auto& x : point) { // 遍历每个海报
int left = point_index[x.first]; // 获取左边界的压缩坐标
int right = point_index[x.second]; // 获取右边界的压缩坐标
for (int i = left; i <= right; i++) { // 在这个范围内
count[i] = kind; // 标记为当前海报
}
kind++; // 下一个海报的标识
}
vector<bool> visit(kind + 1); // 用来记录每个海报是否可见
int ret = 0; // 可见海报的数量
for (int i = 1; i < count.size(); i++) { // 遍历每个位置
if (count[i] != -1 && !visit[count[i]]) { // 如果当前位置有海报且该海报还未被计算过
ret++; // 增加可见海报数量
visit[count[i]] = true; // 标记该海报为已计算
}
}
cout << ret; // 输出结果
}
离散化+线段树
1.
离散化操作还是固定的。也是必须的,将所有的下标即下一个位置收集起来,然后排序+去重,放到 map 里面就是排序+去重操作。
2.
小技巧,先看题目需要输入哪些数据,然后搞一些对应的变量依次把这些数据存储起来,然后正常写代码,当你把代码写完之后,再去考虑哪些数据可以不需要存储,写完之后做减法,减少空间的使用,可以合并的合并,可以省去的省去。
因为空间的过多使用会导致时间复杂度变高。
3.
线段树,先把变量全部写好,然后写类的构造函数,对应这些变量,这些变量的初始化。
然后是 build 出这棵树,根据传入的数组去 build 这棵树,编写 build 函数。
接着是 up,down 函数。
然后是需要实现的功能函数。
4.
小技巧,using ll = long long;
using p = pair<ll, ll>;
尽可能使用 longlong 而不使用 int。
pair 先定义 p。
5.
线段树需要实现的功能函数,定义都是递归维护左,递归维护右,然后 up 维护自己,当然有一个懒更新,每次实现左右递归之前先 down 操作一下。
#include<bits/stdc++.h> // 引入所有标准库
using namespace std;
using ll = long long; // 定义long long 类型的别名 ll
using p = pair<ll, ll>; // 定义pair<long long, long long> 类型的别名 p
ll n, m; // 声明两个长整型变量 n 和 m,分别用于存储墙的长度和海报的数量
vector<p> haibao; // 声明一个存储pair的向量,用于存储每张海报的起始和结束位置
map<ll, ll> point_index; // 声明一个映射,用于坐标压缩
vector<ll> visited; // 声明一个向量,用于记录每个区域的访问状态
ll kind = 1; // 声明一个变量 kind,初始化为 1,用于标记海报的唯一标识
class SegmentTree { // 定义一个线段树类
private:
ll size; // 私有变量,存储线段树的大小
vector<ll> arr; // 私有变量,动态数组,用于初始化线段树的值
struct treenode { // 定义线段树节点的结构
ll sum; // 节点存储的值
ll isupdatelazy; // 延迟更新标志
ll updatelazy; // 延迟更新的值
};
vector<treenode> t; // 动态数组,存储线段树的所有节点
void build(ll l, ll r, ll rt) { // 构建线段树的函数
if (l == r) { // 如果当前区间只有一个元素
t[rt].sum = arr[l]; // 直接将数组值赋给线段树节点
return;
}
ll mid = (l + r) >> 1; // 计算中点
build(l, mid, rt << 1); // 递归构建左子树
build(mid + 1, r, rt << 1 | 1); // 递归构建右子树
up(rt); // 更新当前节点的值
}
void up(ll i) { // 更新节点值的函数
t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum; // 将左右子节点的值相加
}
void down(ll i, ll ln, ll rn) { // 延迟更新的函数
if (t[i].isupdatelazy) { // 如果当前节点有延迟更新标志
t[i << 1].sum = ln * t[i].updatelazy; // 更新左子节点的值
t[i << 1 | 1].sum = rn * t[i].updatelazy; // 更新右子节点的值
t[i << 1].isupdatelazy = t[i << 1 | 1].isupdatelazy = true; // 设置子节点的延迟更新标志
t[i << 1].updatelazy = t[i << 1 | 1].updatelazy = t[i].updatelazy; // 继承延迟更新的值
t[i].isupdatelazy = false; // 清除当前节点的延迟更新标志
}
}
ll _query(ll l, ll r, ll rt) { // 查询线段树中的值
if (l == r) { // 如果到达叶节点
if (t[rt].sum!=-1 && !visited[t[rt].sum]) { // 如果叶节点的值不是 -1 且未被访问过
visited[t[rt].sum] = true; // 标记为已访问
return 1; // 返回 1,表示有一个海报可见
}
return 0; // 否则返回 0
}
ll mid = (l + r) >> 1; // 计算区间的中点
down(rt, mid - l + 1, r - mid); // 应用延迟更新
return _query(l, mid, rt << 1) + _query(mid + 1, r, rt << 1 | 1); // 返回左右子树的查询结果之和
}
void _update(ll L, ll R, ll C, ll l, ll r, ll rt) { // 更新线段树的函数
if (r < L || R < l) { // 如果更新区间与当前区间不重叠
return; // 直接返回
}
if (L <= l && r <= R) { // 如果当前区间完全包含在更新区间内
t[rt].sum = (r - l + 1) * C; // 更新节点的值
t[rt].isupdatelazy = true; // 标记为需要延迟更新
t[rt].updatelazy = C; // 记录延迟更新的值
return;
}
ll mid = (l + r) >> 1; // 计算区间中点
down(rt, mid - l + 1, r - mid); // 应用延迟更新
_update(L, R, C, l, mid, rt << 1); // 更新左子树
_update(L, R, C, mid + 1, r, rt << 1 | 1); // 更新右子树
up(rt); // 更新当前节点
}
public:
SegmentTree(vector<ll>nums) { // 构造函数
size = nums.size() + 1; // 设置线段树的大小
t.resize(size<<2); // 分配足够的空间
arr.resize(size); // 分配足够的空间
for (ll i = 0; i < nums.size(); i++) { // 初始化数组
arr[i + 1] = nums[i];
}
build(1, size - 1, 1); // 构建线段树
}
void update(ll L, ll R, ll C) { // 提供更新的公共接口
_update(L, R, C, 1, size - 1, 1);
}
ll query() { // 提供查询的公共接口
return _query(1, size-1, 1);
}
};
void init() { // 初始化函数,读取输入数据
cin >> n >> m;
for (ll i = 0; i < m; i++) {
ll left, right;
cin >> left >> right;
point_index[left]; // 将左边界加入坐标压缩
point_index[left + 1]; // 将左边界+1加入坐标压缩
point_index[right]; // 将右边界加入坐标压缩
point_index[right + 1]; // 将右边界+1加入坐标压缩
haibao.push_back({ left,right }); // 将海报的范围存储起来
}
}
int main() { // 主函数
init(); // 调用初始化函数
ll index = 1;
for (auto& x : point_index) { // 对每个坐标进行压缩
x.second = index++; // 分配新的索引
}
SegmentTree t(vector<ll>(index, -1)); // 创建线段树实例
for (auto& x : haibao) { // 遍历所有海报
ll left = point_index[x.first]; // 获取压缩后的左边界
ll right = point_index[x.second]; // 获取压缩后的右边界
t.update(left, right, kind++); // 更新线段树,标记海报
}
visited.resize(++kind, 0); // 初始化访问数组
cout << t.query(); // 输出查询结果
}
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!