安吉D13-T3
- 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 天,这一整段岩柱看成一个完整的连续块.之后每一天的操作会对当前所有连续块分别进行同样的操作,彼此互不影响:
- 在这个连续块里,找出坚硬度最小的那一根岩柱;
- 这根岩柱会在这一天被风蚀掉;
- 记下一个二元组 ,其中:
- 表示这根岩柱左边、仍属于这个连续块的岩柱数量;
- 表示这根岩柱右边、仍属于这个连续块的岩柱数量.
因为所有坚硬度两两不同,所以每个连续块里“最小的那根岩柱”总是唯一的.
把这一天所有连续块得到的二元组,按照这些连续块从左到右的顺序依次写下来,就得到这一天的风蚀记录.
当天结束后,被风蚀掉的岩柱消失.剩下的岩柱会分裂成若干个新的连续块,进入下一天.
如果某一天开始时已经没有岩柱了,那么这一天以及之后所有天的风蚀记录都视为空序列.
现在有 次询问.
每次询问给出两段连续区间 和 .
如果这两段区间在每一天得到的风蚀记录都完全相同,就称它们是同谱的.
你需要对每次询问回答这两段区间是否同谱.
输入格式
第一行两个整数 .
第二行 个整数 ,表示每根岩柱的坚硬度.保证它们两两不同.
接下来 行,每行四个整数 ,表示一次询问.
输出格式
对于每次询问输出一行.
如果两段区间同谱,输出 Yes;否则输出 No.
样例
输入 #1
7 53 1 4 2 7 5 61 3 5 71 4 4 72 4 5 71 1 4 43 6 4 7输出 #1
YesNoNoYesNo样例解释
- 区间 的序列是 ,区间 的序列是 .它们第一天都会删掉中间位置的最小值,得到记录 ;第二天左右各剩一个单点块,记录也完全一样,所以它们同谱.
- 区间 和 第一天删掉最小值后,左右剩余规模就不同,因此不同谱.
数据范围
对于所有测试数据,均有:
- 对于所有 ,均有 ,且 两两不同;
- 对于所有询问,均有 .
| 测试点编号 | 特殊性质 | ||
|---|---|---|---|
| 1 | 500 | 500 | 无 |
| 2 | 2000 | 2000 | 无 |
| 3 | 3000 | 3000 | 无 |
| 4, 5 | 50000 | 50000 | A |
| 6 | 100000 | 100000 | B |
| 7 | 100000 | 100000 | C |
| 8 | 100000 | 100000 | D |
| 10 | 100000 | 100000 | E |
| 9, 11 | 200000 | 100000 | 无 |
| 12 | 200000 | 100000 | D |
| 13, 14 | 300000 | 200000 | 无 |
| 15, 16 | 300000 | 200000 | D |
| 17 ~ 20 | 500000 | 500000 | 无 |
特殊性质说明:
- A:对于所有询问,均有 且 .
- B:数组严格递增,即对于所有 ,均有 .
- C:数组严格递减,即对于所有 ,均有 .
- D:数组为交错排列,即 .
- E:对于所有询问,均有 .
观察题目中的风蚀,我们可以发现,这是判断两棵笛卡尔树是否同构.
每一次都以当前区间内最小的点作为根节点,将最小点的左边定为左子树,将最小点的右边定为右子树,接着分别对于左边和右边建树.示意图如下:
![[cartesian-tree1.png]] (图源 oi-wiki)
这棵树在构建时,会将序列中最小的节点 1 取出作为根节点,将 1 左边的节点 作为左子树节点,右边的 作为右子树节点,然后对于左子树和右子树分别递归建树.
笛卡尔树除了上述的构建方法,还可以使用单调栈进行构建.
具体来说,构建一个从栈底到栈顶从小到大排列的单调栈,从左到右依次让元素进栈:
- 若当前栈顶的元素比将要进栈的元素 大,则令栈顶元素出栈.记录最后一个出栈的元素为 .
- 设通过上面一步之后,栈顶元素为 ,则 是 的右子节点, 及其子树为 的左子节点.
示意图如下.实际上,栈中维护的序列就是示意图中红框标出来的部分组成的序列:
![[cartesian-tree2.png]]
(图源 oi-wiki)
这里,两棵树同构是指这两棵树形状相同,但不代表它们每一个点的权值相同.如下面的两棵树是同构的:
显然,每一次如果都构建笛卡尔树并且暴力比较是否同构必定超时,那该怎么做呢?
可以将笛卡尔树转换为一个序列.由单调栈构建法可以从中得出灵感,我们定义 表示区间 第 个元素左边第一个小于 元素的位置(这里的位置时区间内的相对位置,而不是全局的位置).特别的,若没有这样的元素,即 为当前区间的前缀最小值,则令 .显然,在考虑第 个元素时,只需要将其接在元素 的右节点,如果 有右节点,则将原来的右节点挤到左节点即可,这样构造出的笛卡尔树形状是唯一的,因此,对于一个 序列,其笛卡尔树是唯一确定的,这样只需要比较 序列即可.
如果只是暴力计算 序列再比较,每一次询问都是 的,这肯定不行,考虑优化.
我们可以使用单调栈预处理 数组,用于记录每个元素左边第一个比它小的元素.关于 数组的构造,具体来说,维护一个栈底小的单调栈,从前往后遍历每一个点,对于每一个点 ,若栈顶的元素比 大,则将栈顶元素舍弃,这是由于有 的存在,栈顶元素不可能作为后续元素所记录的“每个元素左边第一个比它小的元素”.直到栈顶元素比 小,此时栈顶元素就是 .
则 可以尝试使用 推导.此处为了不造成混淆,令全局下标为 ,局部下标为 ,因此存在 ,此处 是区间左边界.推导过程如下:
- 若 ,则表示元素 左边第一个比它小的元素不在 之中.因此特殊的,置 .
- 否则,.
此处变量 每一次查询都会变化,尝试将其优化.
这样,若设 ,特别的,若 ,则 .则公式简化为:
由于 和 一一对应,因此知道一个确定的 序列也可以确定笛卡尔树的形状.显然, 数组可以预处理出来.我们可以使用类似字符串哈希的方法来快速判定两个序列是否相等.若设哈希基数为 ,则 序列哈希可以表示为:
对于询问 ,其哈希值为( 是指示函数,当且仅当 成立时,其值为 1,否则为 0):
因此只需要求出两个区间的哈希,再一比较,若相等,就可以认为这两个区间符合题意.如何快速求出两个区间的哈希呢?
如果暴力枚举公式中的 ,则单次查询复杂度回到了 ,白优化了.若设 ,那么就需要快速求出 区间内 的项的区间和.由于 随着 的增大而增大,因此满足 的项数量肯定是随 增大而减少的.且已经不满足条件的项不可能在之后的过程中重新满足条件.这可以使用树状数组来优化.具体来说,在树状数组中储存所有在当前还满足要求的 :
- 在遍历开始前,将所有的 以 作为键, 作为值加入到树状数组.
- 离线所有的查询,将每一次查询中的区间拆开,一共 个区间,将这些区间按照 从小到大排序.依照这个顺序依次解决每一个区间的哈希值.
- 对于每一个左端点为 的一组请求,我们依次处理,每一次处理时,依照公式,先用树状数组计算 之间的区间和,再乘 ,即 的逆元.
- 解决完一组请求之后,设下一组请求的左端点为 ,则满足 的 值都将不满足条件,将它们从树状数组中删除,即置为 0.
当然,为了防止冲突,除了使用双哈希之外,对于每一个询问,还需要特判这两个区间长度是否相等,如果不相等,直接为 No.(序列 和 这两者的哈希是相同的)
有的时候,我们需要求出区间 中所有符合某种条件的元素之和,若这个条件符合单调性,即随着某一个变量的增大或减小,符合条件的元素逐渐变多,且开始符合条件的元素不可能在之后的某一瞬间不符合条件,则称这个条件具有单调性.这个与之相关的变量成为单调变量.
面对有单调性条件的区间部分元素求和问题,我们可以使用树状数组.具体来说,将所有元素按单调变量的顺序递增或递减排列,当遍历到某一个元素时,将从此刻起开始符合条件的元素加入树状数组.
标程
#include <bits/stdc++.h>typedef long long ll;#define inv(x, MOD) (fastpow(x, MOD - 2, MOD))#define w(x, MOD) (fastpow(BASE, x, MOD) * (x - p[x]) % MOD)using namespace std;const int N = 5e5 + 100;const int BASE = 127;const int MOD1 = 1e9 + 7;const int MOD2 = 1e9 + 9;int n, q, a[N], p[N];bool ans[N];vector<int> buc[N];map<int, ll> mp1, mp2;struct Query { int l, r, id; friend bool operator<(const Query a, const Query b) { return a.l < b.l; }};vector<Query> qry;
ll fastpow(int a, int p, int MOD) { ll ans = 1, now = a; while (p > 0) { if (p & 1) { (ans *= now) %= MOD; } p >>= 1; (now *= now) %= MOD; } return ans;}
struct BIT { ll f[N]; int MOD; BIT(int MOD) : MOD(MOD) {} int lowbit(int x) { return x & (-x); } void Modify(int pos, int v) { if (v < 0) v += MOD; for (int i = pos; i <= n; i += lowbit(i)) { (f[i] += v) %= MOD; } } int Query(int pos) { if (pos == 0) return 0; ll ans = 0; for (int i = pos; i >= 1; i -= lowbit(i)) { (ans += f[i]) %= MOD; } return ans; } int Query(int l, int r) { return (Query(r) - Query(l - 1) + MOD) % MOD; }} bit1(MOD1), bit2(MOD2);
signed main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> n >> q; for (int i = 1; i <= n; i++) { cin >> a[i]; } stack<int> sta; for (int i = 1; i <= n; i++) { while (!sta.empty() && a[sta.top()] >= a[i]) { sta.pop(); } if (sta.empty()) { p[i] = 0; } else { p[i] = sta.top(); } sta.push(i); } for (int i = 1; i <= q; i++) { int l1, l2, r1, r2; cin >> l1 >> r1 >> l2 >> r2; if (r1 - l1 != r2 - l2) { ans[i] = false; continue; } qry.push_back({l1, r1, i}); qry.push_back({l2, r2, i}); } sort(qry.begin(), qry.end()); for (int i = 1; i <= n; i++) { if (p[i]) { bit1.Modify(i, w(i, MOD1)); bit2.Modify(i, w(i, MOD2)); buc[p[i]].push_back(i); } } int cur_l = 1; for (auto query : qry) { while (cur_l != -1 && cur_l != query.l) { for (auto x : buc[cur_l]) { bit1.Modify(x, -w(x, MOD1)); bit2.Modify(x, -w(x, MOD2)); } cur_l++; } ll hs1 = inv(fastpow(BASE, query.l, MOD1), MOD1) * bit1.Query(query.l, query.r) % MOD1; ll hs2 = inv(fastpow(BASE, query.l, MOD2), MOD2) * bit2.Query(query.l, query.r) % MOD2; if (mp1.count(query.id)) { if (mp1[query.id] == hs1 && mp2[query.id] == hs2) { ans[query.id] = true; } else { ans[query.id] = false; } } else { mp1[query.id] = hs1; mp2[query.id] = hs2; } } for (int i = 1; i <= q; i++) { if (ans[i]) { cout << "Yes" << endl; } else { cout << "No" << endl; } } return 0;}文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!


