三元上升子序列

GitHub跳转原题关系图返回列表

离散化后用两次 Fenwick 扫描统计每个中间位置左侧更小和右侧更大的数量。

OJ: luogu

题目 ID: P1637

难度:普及+/提高

标签:树状数组离散化计数python

日期: 2026-07-16 23:59

题意

统计 i < j < ka[i] < a[j] < a[k] 的三元组数量。

思路

固定中间位置 j。左侧比 a[j] 小的个数乘右侧比它大的个数,就是以 j 为中间点的方案数。离散化后,正向 Fenwick 查询严格更小的排名,反向 Fenwick 查询严格更大的排名,最后求乘积之和。

Python 知识

  • sorted(set(values)) 加字典推导式完成离散化。
  • 反向扫描时用 seen - prefix(rank) 处理严格大于,避免重复值被计入。
  • zip(left_count, right_count) 直接组合两个计数列表。

代码

python
import sys


data = list(map(int, sys.stdin.buffer.read().split()))
n, values = data[0], data[1:]
rank = {value: index for index, value in enumerate(sorted(set(values)), 1)}
size = len(rank)


def add(tree, index):
    while index <= size:
        tree[index] += 1
        index += index & -index


def query(tree, index):
    result = 0
    while index:
        result += tree[index]
        index -= index & -index
    return result


left_count = []
tree = [0] * (size + 1)
for value in values:
    position = rank[value]
    left_count.append(query(tree, position - 1))
    add(tree, position)

right_count = [0] * n
tree = [0] * (size + 1)
seen = 0
for index in range(n - 1, -1, -1):
    position = rank[values[index]]
    right_count[index] = seen - query(tree, position)
    add(tree, position)
    seen += 1

print(sum(left * right for left, right in zip(left_count, right_count)))

原有 C++ 版本仍保留:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-16 23:46
 * update_at: 2026-07-16 23:46
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    return 0;
}

复杂度

离散化 O(n log n),两次 Fenwick 扫描 O(n log n),空间 O(n)

总结

三元组计数的关键是固定中间点,把三维条件拆成两个一维前缀统计。