1 条题解
-
0
题解说明
问题描述:
给定两个长度为 的数组 和 ,以及若干区间查询 。要求对每个查询计算区间内的某种统计值(具体为 的某种动态更新结果)。代码通过线段树 单调栈 离线处理实现高效查询。核心思路:
单调栈预处理:- 对数组 ,计算 :表示 左侧第一个比 大的位置。
- 对数组 ,计算 :表示 左侧第一个比 大的位置。
- 这两个数组用于确定在处理到位置 时,哪些区间需要更新。
线段树维护信息:
- 每个节点存储四类信息:
- :区间的最终统计值
- :区间内 的和
- :区间内 的和
- :区间内 的和
- 通过懒标记 支持区间赋值和增量更新,保证更新效率。
更新操作:
- 当处理到位置 时:
- 在区间 内,将 统一设为
- 在区间 内,将 统一设为
- 在区间 内,累加
- 这些操作通过线段树的懒标记实现。
查询处理:
- 将所有查询按右端点 分组存储。
- 遍历 ,每次完成上述更新后,回答所有右端点为 的查询。
- 查询时直接在线段树上取区间 的 值。
算法流程:
输入 和数组 。
用单调栈分别计算 和 。
输入 个查询,按右端点分组存储。
遍历 :- 执行三次区间更新(更新 、更新 、累加 )。
- 回答所有右端点为 的查询。
输出所有查询结果。
复杂度分析:
- 单调栈预处理:
- 每个位置 的更新操作:(线段树区间更新)
- 每个查询:(线段树区间查询)
- 总复杂度:,在 时可行。
实现细节与注意点:
- 使用 存储结果,避免溢出。
- 懒标记 设计较复杂,需正确合并和下传。
- 离线处理查询,保证每个查询只需一次线段树查询。
- 单调栈保证 的计算为 。
总结:
该算法结合了单调栈和线段树的优势:- 单调栈快速确定更新区间
- 线段树支持复杂的区间赋值与增量操作
- 离线处理保证查询高效
整体实现复杂但思路清晰,能在大数据范围内高效回答区间查询。
#include <bits/stdc++.h> using namespace std; // 类型定义与宏定义 #define ull unsigned long long // 无符号长整型,用于存储较大的计算结果 #define mp make_pair // 快速创建pair的宏 #define pb push_back // 向量尾部插入元素的宏 #define ls (cur<<1) // 线段树左孩子节点索引(cur*2) #define rs (cur<<1|1) // 线段树右孩子节点索引(cur*2+1) #define mid ((l+r)>>1) // 区间中点计算((l+r)/2) /** * 快速读入函数 * 功能:比cin/fscanf更快地读取整数,适用于大量输入场景 * 原理:直接读取字符流,过滤非数字字符后转换为整数 */ inline int read() { int x = 0; char ch = getchar(); // 从标准输入读入字符 // 跳过非数字字符 while (!isdigit(ch)) ch = getchar(); // 读取数字字符并转换为整数 while (isdigit(ch)) x = x * 10 + (ch ^ 48), ch = getchar(); // (ch^48)等价于(ch-'0') return x; } /** * 输出函数 * 功能:将无符号长整型数输出到标准输出 * 处理:将数字转换为字符串后反转(因为计算时是从低位到高位),再输出 */ void print(ull x) { if (x == 0) { cout << 0 << endl; return; } string s; while (x > 0) { s += (x % 10) + '0'; // 取出末位数字并转为字符 x /= 10; } reverse(s.begin(), s.end()); // 反转得到正确顺序 cout << s << endl; } // 常量与结构体定义 const int N = 5e5 + 5; // 最大数据规模(5*10^5 +5,预留空间) /** * 线段树节点结构体 * 存储区间的统计信息: * S:区间的计算结果总和 * XY:区间内a[i]*b[i]的总和 * X:区间内a[i]的总和 * Y:区间内b[i]的总和 */ struct node { ull S, XY, X, Y; } Tree[N << 2]; // 线段树数组(大小为4*N,满足区间划分需求) /** * 懒标记结构体(延迟更新标记) * 用于线段树的区间更新优化,存储需要传递的更新参数: * cx:a[i]的固定值(若不为0则表示区间a[i]统一设为cx) * cy:b[i]的固定值(若不为0则表示区间b[i]统一设为cy) * pxy:a[i]*b[i]的系数 * px:a[i]的系数 * py:b[i]的系数 * p:常数项 */ struct TAG { ull cx, cy, pxy, px, py, p; } tag[N << 2]; // 懒标记数组 // 全局变量 int a[N], b[N]; // 存储原始数据数组 int lasa[N], lasb[N]; // lasa[i]:i左侧第一个a值大于a[i]的位置;lasb[i]同理 int n, q, T, sta[N]; // n:数据长度;q:查询次数;T:测试用例数;sta:单调栈数组 ull Ans[N]; // 存储查询结果 vector<pair<int, int>> Q[N]; // 离线查询存储:Q[r]存储所有右边界为r的查询(左边界l,查询id) /** * 线段树上传操作 * 功能:用左右孩子节点的信息更新父节点信息 */ void pushup(int cur) { Tree[cur].S = Tree[ls].S + Tree[rs].S; // 父节点S = 左孩子S + 右孩子S Tree[cur].X = Tree[ls].X + Tree[rs].X; // 父节点X = 左孩子X + 右孩子X Tree[cur].Y = Tree[ls].Y + Tree[rs].Y; // 父节点Y = 左孩子Y + 右孩子Y Tree[cur].XY = Tree[ls].XY + Tree[rs].XY; // 父节点XY = 左孩子XY + 右孩子XY } /** * 应用懒标记到当前节点 * @param cur:当前节点索引 * @param len:当前节点覆盖的区间长度 * @param t:需要应用的懒标记 * 功能:根据懒标记更新当前节点的统计信息,并合并到节点自身的懒标记中 */ void Add(int cur, int len, TAG t) { auto &[cx, cy, pxy, px, py, p] = t; // 解包新标记 auto &[Cx, Cy, Pxy, Px, Py, P] = tag[cur]; // 解包当前节点已有标记 // 合并标记:根据当前节点已有标记的状态(是否有固定a/b值)计算新标记的影响 if (Cx && Cy) // 已有固定a和b值 P += Cx * Cy * pxy + Cx * px + Cy * py + p; else if (Cx) // 已有固定a值 P += Cx * px + p, Py += Cx * pxy + py; else if (Cy) // 已有固定b值 P += Cy * py + p, Px += Cy * pxy + px; else // 无固定a/b值 P += p, Px += px, Py += py, Pxy += pxy; // 更新固定值(若新标记有固定值则覆盖) if (cx) Cx = cx; if (cy) Cy = cy; // 更新当前节点的统计信息 auto &[S, XY, X, Y] = Tree[cur]; S += XY * pxy + X * px + Y * py + len * p; // 累加新标记对S的影响 // 根据新标记的固定值更新XY、X、Y if (cx && cy) // 同时固定a和b XY = cx * cy * len, X = cx * len, Y = cy * len; else if (cx) // 仅固定a XY = cx * Y, X = cx * len; else if (cy) // 仅固定b XY = cy * X, Y = cy * len; } /** * 线段树下传操作 * @param cur:当前节点索引 * @param len1:左孩子覆盖的区间长度 * @param len2:右孩子覆盖的区间长度 * 功能:将当前节点的懒标记传递给左右孩子,实现延迟更新 */ void pushdown(int cur, int len1, int len2) { // 若当前节点有标记,则下传给孩子 if (tag[cur].cx || tag[cur].cy || tag[cur].pxy || tag[cur].px || tag[cur].py || tag[cur].p) { Add(ls, len1, tag[cur]); // 左孩子应用标记 Add(rs, len2, tag[cur]); // 右孩子应用标记 tag[cur] = {0, 0, 0, 0, 0, 0}; // 清空当前节点标记 } } /** * 线段树区间更新 * @param cur:当前节点索引 * @param l:当前节点覆盖的区间左边界 * @param r:当前节点覆盖的区间右边界 * @param ql:需要更新的区间左边界 * @param qr:需要更新的区间右边界 * @param t:更新使用的懒标记 * 功能:对[ql, qr]区间应用标记t的更新 */ void update(int cur, int l, int r, int ql, int qr, TAG t) { // 若当前区间完全在更新区间内,直接应用标记 if (ql <= l && r <= qr) return Add(cur, r - l + 1, t), void(); // 下传标记,确保孩子节点信息正确 pushdown(cur, mid - l + 1, r - mid); // 递归更新左/右孩子 if (qr <= mid) update(ls, l, mid, ql, qr, t); else if (mid < ql) update(rs, mid + 1, r, ql, qr, t); else update(ls, l, mid, ql, qr, t), update(rs, mid + 1, r, ql, qr, t); // 上传更新父节点信息 pushup(cur); } /** * 线段树区间查询 * @param cur:当前节点索引 * @param l:当前节点覆盖的区间左边界 * @param r:当前节点覆盖的区间右边界 * @param ql:查询区间左边界 * @param qr:查询区间右边界 * @return:区间[ql, qr]的S值总和 */ ull query(int cur, int l, int r, int ql, int qr) { // 若当前区间完全在查询区间内,直接返回当前节点的S值 if (ql <= l && r <= qr) return Tree[cur].S; // 下传标记,确保孩子节点信息正确 pushdown(cur, mid - l + 1, r - mid); // 递归查询左/右孩子并返回总和 if (qr <= mid) return query(ls, l, mid, ql, qr); else if (mid < ql) return query(rs, mid + 1, r, ql, qr); else return query(ls, l, mid, ql, qr) + query(rs, mid + 1, r, ql, qr); } int main() { // 读取输入(测试用例数和数据长度) T = read(), n = read(); int top = 0; // 单调栈指针 // 计算lasa数组:用单调栈找每个位置左侧第一个大于a[i]的元素位置 for (int i = 1; i <= n; i++) { a[i] = read(); // 维护单调栈(栈内元素a值递减):弹出所有小于当前a[i]的元素 while (top && a[sta[top]] < a[i]) top--; lasa[i] = sta[top]; // 栈顶即为左侧第一个更大元素的位置(0表示无) sta[++top] = i; // 当前位置入栈 } // 计算lasb数组:同理,找每个位置左侧第一个大于b[i]的元素位置 top = 0; for (int i = 1; i <= n; i++) { b[i] = read(); while (top && b[sta[top]] < b[i]) top--; lasb[i] = sta[top]; sta[++top] = i; } // 读取查询并离线存储(按右边界r分组) q = read(); for (int i = 1, l, r; i <= q; i++) { l = read(), r = read(); Q[r].pb(mp(l, i)); // 右边界为r的查询存到Q[r],记录左边界l和查询id } // 按右边界r递增处理,同时更新线段树并回答查询 for (int r = 1; r <= n; r++) { // 更新区间[lasb[r]+1, r]:设置a[i] = a[r] update(1, 1, n, lasa[r] + 1, r, (TAG){a[r], 0, 0, 0, 0, 0}); // 更新区间[lasb[r]+1, r]:设置b[i] = b[r] update(1, 1, n, lasb[r] + 1, r, (TAG){0, b[r], 0, 0, 0, 0}); // 更新区间[1, r]:累加a[i]*b[i](pxy=1表示系数为1) update(1, 1, n, 1, r, (TAG){0, 0, 1, 0, 0, 0}); // 回答所有右边界为r的查询 for (auto [l, id] : Q[r]) Ans[id] = query(1, 1, n, l, r); } // 输出所有查询结果 for (int i = 1; i <= q; i++) print(Ans[i]); return 0; }
- 1
信息
- ID
- 3169
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者