洛谷p1637三元上升子序列

本文介绍了一种使用线段树数据结构求解序列中符合条件(i<j<k且a[i]<a[j]<a[k])的三元组数量的方法。通过两次遍历序列,利用线段树进行插入和查询操作,有效地统计了满足条件的三元组数量。

题目链接

求序列中 i < j < k 且 a [ i ] < a [ j ] < a [ k ] 的 三 元 组 数 量 i< j<k且a[i]<a[j]<a[k]的三元组数量 i<j<ka[i]<a[j]<a[k]

类似于线段树求逆序对,前后跑两遍即可。

AC代码:

#define MAX 500010
struct node
{
   
   
    int k, l, r, sum;
} tr[4 * MAX];
int a[MAX];
int b[MAX];

void pushup(int k)
{
   
   
    tr[k].sum = tr[k * 2].sum + tr[k * 2 + 1].sum;
}
void build(
<think>根据用户查询,用户希望了解洛谷1637题的题解。该题是“三元上升子序列”问题。结合引用内容,特别是引用[3]和引用[4],我们可以得知该题需要使用离散化、树状数组等技巧来高效求解。 引用[3]提供了离散化的模板代码,并明确提到该题是【luogu P1637 三元上升子序列】。引用[4]则指出使用树状数组来求Left和Right数组,总时间复杂度为$O(n \log n)$。 因此,解题思路如下: 1. 离散化:将原始数组映射到有序的整数序列,以降低数值范围。 2. 使用树状数组统计每个位置左侧比它小的数的个数(Left数组)。 3. 使用树状数组统计每个位置右侧比它大的数的个数(Right数组)。 4. 对于每个位置i,以i为中间元素的三元上升子序列数量为Left[i] * Right[i],然后累加得到答案。 具体步骤: - 离散化:将数组A复制到数组B,对B排序去重,然后对A中每个元素用其在B中的下标替换。 - 从左到右遍历:用树状数组维护已遍历元素,对于A[i],Left[i]等于当前树状数组中[1, A[i]-1]区间内的元素个数(即左侧比A[i]小的元素个数)。 - 从右到左遍历:同样用树状数组,对于A[i],Right[i]等于当前树状数组中[A[i]+1, n]区间内的元素个数(即右侧比A[i]大的元素个数)。 - 注意:离散化后,元素范围变为1~m,树状数组大小应设为m。 下面给出完整代码和详细注释:</think>### 洛谷 P1637 三元上升子序列题解 **问题描述** 给定长度为 $n$ 的整数序列 $a_1, a_2, \dots, a_n$,求满足 $i < j < k$ 且 $a_i < a_j < a_k$ 的三元组数量[^3]。 --- #### 核心算法:离散化 + 树状数组 **时间复杂度**:$O(n \log n)$ **空间复杂度**:$O(n)$ --- ### 解题步骤 #### 1. 离散化处理 将原始数据映射到紧凑的整数范围,避免值域过大导致空间浪费[^2][^3]。 **实现代码**: ```cpp sort(B + 1, B + 1 + n); // 复制并排序原始数组 m = unique(B + 1, B + 1 + n) - B - 1; // 去重 for (int i = 1; i <= n; i++) A[i] = lower_bound(B + 1, B + m + 1, A[i]) - B; // 映射到新值域 ``` #### 2. 计算左侧较小元素数量(Left 数组) 用树状数组统计每个位置 $i$ **左侧**比 $a_i$ 小的元素个数[^4]。 **操作**: - 从左到右遍历,对每个 $a_i$: - 查询 $[1, a_i-1]$ 区间和 → `Left[i] = sum(c1, a_i-1)` - 将 $a_i$ 加入树状数组 #### 3. 计算右侧较大元素数量(Right 数组) 统计每个位置 $i$ **右侧**比 $a_i$ 大的元素个数。 **操作**: - 从右到左遍历,对每个 $a_i$: - 查询 $[a_i+1, m]$ 区间和 → `Right[i] = n - i - (sum(c2, a_i) - 1)` - 将 $a_i$ 加入树状数组 #### 4. 统计最终答案 对每个中间位置 $j$($2 \le j \le n-1$): $$ \text{ans} += \text{Left}[j] \times \text{Right}[j] $$ 即以 $a_j$ 为中间值的三元组数量 = 左侧比 $a_j$ 小的数量 × 右侧比 $a_j$ 大的数量[^4]。 --- ### 完整代码(C++) ```cpp #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 3e4 + 10; int n, m, A[maxn], _A[maxn]; // _A为离散化辅助数组 int c1[maxn], c2[maxn]; // 树状数组(左侧/右侧) int Left[maxn], Right[maxn]; // 存储左右计数 // 离散化映射函数 inline int mapping(int val) { return lower_bound(_A + 1, _A + m + 1, val) - _A; } // 树状数组核心操作 inline int lowbit(int x) { return x & -x; } void add(int *C, int pos, int val) { while (pos <= maxn) { C[pos] += val; pos += lowbit(pos); } } int sum(int *C, int pos) { int res = 0; while (pos > 0) { res += C[pos]; pos -= lowbit(pos); } return res; } int main() { cin >> n; for (int i = 1; i <= n; i++) { scanf("%d", &A[i]); _A[i] = A[i]; // 复制原始数组 } // 离散化步骤 sort(_A + 1, _A + n + 1); m = unique(_A + 1, _A + n + 1) - _A - 1; // 计算Left数组(左侧比A[i]小的数) for (int i = 1; i <= n; i++) { int pos = mapping(A[i]); add(c1, pos, 1); Left[i] = sum(c1, pos - 1); // 查询[1, pos-1]的和 } // 计算Right数组(右侧比A[i]大的数) for (int i = n; i >= 1; i--) { int pos = mapping(A[i]); add(c2, pos, 1); Right[i] = (n - i) - (sum(c2, pos) - 1); // 总数 - 小于等于当前值的数 } // 统计三元组数量 long long ans = 0; for (int i = 2; i < n; i++) ans += 1LL * Left[i] * Right[i]; cout << ans; return 0; } ``` --- ### 关键点解析 1. **离散化必要性**:原始数据值域可能很大(如 $10^9$),离散化将其映射到 $[1, n]$,确保树状数组空间可控[^2]。 2. **树状数组作用**:高效维护前缀和,单次操作时间复杂度 $O(\log n)$[^4]。 3. **Left/Right 数组含义**: - `Left[i]`:位置 $i$ **左侧**比 $a_i$ 小的元素数量 - `Right[i]`:位置 $i$ **右侧**比 $a_i$ 大的元素数量 --- ### 相关问题 1. **离散化后如何处理重复元素?** `lower_bound` 返回首个 $\geq$ 目标值的位置,相同元素映射到相邻位置,不影响大小关系统计。 2. **为什么树状数组大小设为 $3 \times 10^4$?** 题目规定 $n \leq 30000$,离散化后值域为 $[1, n]$,需覆盖最大索引[^2]。 3. **如何扩展为 $k$ 元上升子序列?** 使用动态规划:定义 $dp[k][i]$ 表示以 $a_i$ 结尾的长度为 $k$ 的上升子序列数量,结合树状数组优化转移。 [^1]: 树状数组与离散化常用于解决逆序对问题 [^2]: 离散化避免空间爆炸,将大值域映射到紧凑区间 [^3]: 洛谷 P1637 的离散化模板实现 [^4]: 树状数组高效统计左右两侧满足条件的元素数量
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hesorchen

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

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

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

打赏作者

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

抵扣说明:

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

余额充值