1 条题解
-
0
题解说明
问题背景:
给定一个排列数组 ,需要对其划分区间并计算每个位置的某种统计量。代码通过复杂的数据结构(树状数组、链表、并查集)和双向处理技巧,完成对每个区间的答案计算。核心思路:
区间划分- 遍历数组 ,维护当前最大值 。
- 当 时,说明 是一个完整区间(排列的前缀最大值刚好覆盖到 )。
- 将该区间作为独立子问题处理。
区间标准化
- 将区间 内的值映射为 的连续整数,存入数组 。
- 初始化答案数组 ,表示基础贡献。
solve 函数(核心)
- 使用树状数组维护前缀和,用于快速统计小于某值的元素个数。
- 使用 记录每个位置向左/向右扩展到的最小值位置。
- 每个元素初始为独立集合,集合存储区间 。
- 从大到小遍历值,逐步合并集合,更新答案。
- 合并时使用链表和并查集思想,保证小集合并入大集合,减少复杂度。
双向处理
- 为了覆盖所有情况,除了正向处理,还需要反向处理:
- 将 映射为 ,再整体反转。
- 同时反转 数组,保证对应关系。
- 再次调用 solve,补充计算答案。
- 最终再反转回来,得到完整答案。
输出
- 每个区间处理完毕后,输出该区间的所有 。
复杂度分析:
- 每个区间内的处理主要依赖树状数组()和集合合并(均摊 )。
- 整体复杂度约 ,适合 的数据范围。
实现细节:
- 树状数组用于快速统计前缀和。
- 数组通过逆序遍历构建,保证能快速找到左右边界。
- 合并集合时维护 (标记值),避免重复更新,保证效率。
- 双向处理是关键技巧,确保所有情况都被覆盖。
#include <bits/stdc++.h> // 循环宏定义:rep为正向循环(i从a到b递增),per为反向循环(i从a到b递减) #define rep(i,a,b) for(int i=(a);i<=(b);++i) #define per(i,a,b) for(int i=(a);i>=(b);--i) #define pb push_back // 向量尾部插入元素 #define P make_pair // 创建pair对(简化写法) using namespace std; typedef long long ll; // 定义长整型别名ll const int N = 2e5 + 3; // 数组最大规模(2e5+3,适配题目数据范围) // 快速读取整数(处理正负号) inline ll read() { ll x = 0; // 存储读取结果 bool f = 0; // 符号标记(0为正,1为负) char c = getchar(); // 读取单个字符 // 跳过非数字字符,检测负号 while (c < '0' || c > '9') f = (c == '-'), c = getchar(); // 遇'-'则标记f为1 // 读取数字部分,累加形成最终结果 while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); // 字符转数字并累加 return f ? -x : x; // 返回带符号的结果 } int nn, p[N]; // nn:原始数组长度;p[N]:原始输入数组 ll ans[N]; // ans[N]:最终答案数组(存储每个位置的结果) int n, a[N], ra[N], ls[N], rs[N]; // n:当前处理区间长度;a[N]:当前区间的数组; // ra[N]:a数组的逆映射(ra[val] = 下标); // ls[N]:每个位置的左边界;rs[N]:每个位置的右边界 #define lb(x) (x&-(x)) // 宏定义:获取x的最低位1(树状数组专用) int t[N]; // 树状数组:用于快速查询前缀和、更新元素 // 树状数组更新:在x位置加k inline void ad(int x, int k) { for (; x <= n; x += lb(x)) // 沿最低位1向上更新父节点 t[x] += k; } // 树状数组查询:查询1~x的前缀和 inline int qr(int x) { int A = 0; // 存储查询结果 for (; x; x -= lb(x)) // 沿最低位1向下累加子节点值 A += t[x]; return A; } // 初始化函数:预处理ra、rs、ls数组 inline void init() { // 1. 构建ra数组(a的逆映射:ra[值] = 对应的下标) rep(i, 1, n) { ra[a[i]] = i; t[i] = 0; // 初始化树状数组(后续会重新赋值) } // 2. 构建rs数组:rs[i]表示i右侧(含i)第一个最小值的下标 int mi = n; // 记录当前最小值的下标,初始为n per(i, n, 1) { // 反向遍历(从后往前) if (a[i] < a[mi]) // 若当前a[i]更小,更新最小值下标 mi = i; rs[i] = mi; // 记录i位置的右边界 } // 3. 构建ls数组:ls[i]表示i左侧(含i)第一个最小值的下标 mi = n; // 重置最小值下标 per(i, n, 1) { // 反向遍历(从后往前) mi = min(mi, ra[i]); // ra[i]是值i的下标,取当前最小值下标 ls[ra[i]] = mi; // 记录值i对应下标的左边界 } } // 链表与并查集相关数组:用于区间合并操作 int hd[N], pre[N], X[N], Y[N]; // hd[N]:链表头;pre[N]:前驱指针;X/Y[N]:区间左右端点 int h2[N], p2[N], sz[N]; // h2/p2[N]:另一组链表(用于合并);sz[N]:集合大小 ll tg[N]; // tg[N]:标记值(用于批量更新答案) // 合并函数:将集合i合并到集合j inline void mer(int j, int i) { // 按大小合并:小集合合并到大集合(减少操作次数) if (sz[j] < sz[i]) { swap(h2[i], h2[j]); // 交换链表头 swap(sz[i], sz[j]); // 交换集合大小 swap(tg[i], tg[j]); // 交换标记值 } // 遍历集合i的所有元素,合并到集合j for (int x = h2[i], t = p2[x]; x; x = t, t = p2[x]) { ans[x] += tg[i] - tg[j];// 更新元素x的答案(调整标记差值) p2[x] = h2[j]; // 接入集合j的链表 h2[j] = x; // 更新集合j的链表头 } sz[j] += sz[i]; // 更新集合j的大小 } // 核心解决函数:处理当前区间的答案计算 inline void solve() { init(); // 先调用init()预处理ra、rs、ls数组 // 初始化每个元素为独立集合 rep(i, 1, n) { hd[i] = X[i] = Y[i] = h2[i] = i; // 链表头、区间端点、另一链表头均指向自身 pre[i] = p2[i] = tg[i] = 0; // 前驱、链表指针、标记初始化为0 sz[i] = 1; // 集合大小初始为1 ad(i, 1); // 树状数组初始化:每个位置加1(表示存在) } // 从大到小遍历值xx(控制合并顺序) per(xx, n, 1) { ad(a[xx], -1); // 树状数组中删除a[xx]位置(标记为不存在) static int b[N << 1]; // 临时数组:存储当前要处理的集合 int tt = 0, hh = 1; // tt:数组尾指针;hh:数组头指针(模拟队列) // 将以xx为头的链表元素存入b数组 for (int i = hd[xx]; i; i = pre[i]) b[++tt] = i; reverse(b + 1, b + tt + 1); // 反转数组(调整处理顺序) // 处理b数组中的所有集合 while (hh <= tt) { int i = b[hh++]; // 取出当前要处理的集合i int x = X[i], y = Y[i]; // 集合i对应的区间[x,y] // 查询树状数组中1~(a[y]-1)的和(统计符合条件的元素个数) int res = qr(a[y] - 1); int x2 = ls[y], y2 = rs[x]; // 计算新的区间边界[x2,y2] tg[i] += res; // 累加当前集合的标记值(贡献到答案) // 情况1:x == x2(左边界已达最小) if (x == x2) { // 子情况1.1:y == y2(右边界也达最小,整个区间处理完毕) if (y == y2) { // 所有位置的答案加上当前集合的标记值,直接返回 rep(j, 1, n)ans[j] += tg[i]; return; } // 子情况1.2:右边界未达最小,处理末尾集合 int j = b[tt]; if (Y[j] == y2) // 若集合j的右边界是y2,合并j到i mer(j, i); else { X[i] = x2, Y[i] = y2; // 更新集合i的区间边界 b[++tt] = i; // 重新加入b数组待处理 } } // 情况2:x != x2(左边界未达最小) else { int j = hd[x2]; // 取x2为头的集合j if (Y[j] == y2) // 若集合j的右边界是y2,合并j到i mer(j, i); else { X[i] = x2, Y[i] = y2; // 更新集合i的区间边界 pre[i] = hd[x2]; // 接入x2对应的链表 hd[x2] = i; // 更新链表头 } } } } } // 区间初始化与双向处理函数:处理[L,R]区间,计算最终答案 inline void init(int l, int r) { n = r - l + 1; // 当前处理区间的长度(从l到r共n个元素) // 1. 构建当前区间的a数组(将原始p数组的[l,r]段映射为1~n的连续下标) rep(i, l, r)a[i - l + 1] = p[i] - l + 1; // 初始化答案数组:每个位置初始值为n-1(基础贡献) rep(i, 1, n)ans[i] = n - 1; solve(); // 正向处理当前区间,计算部分答案 // 2. 反向处理(转换为逆问题,覆盖所有情况) rep(i, 1, n)a[i] = n - a[i] + 1; // 数组值反转(1→n,2→n-1...) reverse(a + 1, a + n + 1); // 数组整体反转 reverse(ans + 1, ans + n + 1); // 答案数组同步反转 solve(); // 反向处理,补充计算答案 // 3. 恢复答案数组顺序,输出当前区间的结果 reverse(ans + 1, ans + n + 1); rep(i, 1, n)printf("%lld ", ans[i]); } int main() { nn = read(); // 读取原始数组长度 rep(i, 1, nn)p[i] = read();// 读取原始数组p int mx = 0, lst = 0; // mx:当前最大值;lst:上一个区间的结束位置 // 划分“最大值为当前下标”的区间(如[1,3]、[4,5]等) rep(i, 1, nn) { mx = max(mx, p[i]); // 更新当前最大值 // 若最大值等于当前下标i,说明[lst+1, i]是一个完整区间,处理该区间 if (mx == i) init(lst + 1, i), lst = i; } }
- 1
信息
- ID
- 3189
- 时间
- 2000ms
- 内存
- 1024MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者