【六十六】【算法分析与设计】上帝造题的七分钟 2 / 花神游历各国 - 洛谷,The Child and Sequence - 洛谷,[HAOI2014] 贴海报 - 洛谷,线段树的应用,离散化操作

上帝造题的七分钟 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. 格式为 1 l r,表示求 \sum \limits _{i=l}^{r} a_ii=lrai 的值并输出。

  2. 格式为 2 l r x,表示对区间 [l,r][lr] 内每个数取模,模数为 xx

  3. 格式为 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,mnm,分别表示数列长度和操作次数。

第二行给出长为 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 墙。

张贴规则如下:

  1. electoral 墙是一个长度为 $$$$ 个单位的长方形,每个单位记为一个格子;

  1. 所有张贴的海报的高度必须与 electoral 墙的高度一致的;

  1. 每张海报以 A B 表示,即从第 $$$$ 个格子到第 $$$$ 个格子张贴海报;

  1. 后贴的海报可以覆盖前面已贴的海报或部分海报。

现在请你判断,张贴完所有海报后,在 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(); // 输出查询结果
}

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 40
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值