本节默认会基础 Fenwick。若
lowbit和区间摘要尚不牢,先完成 课前零食:会变化的区间和。
在已掌握树状数组单点加、前缀和的基础上,学会把三元关系、左右频次关系和带负数子段和统一改写成“扫描线 + 已处理集合中的秩查询”。
前置:Fenwick Tree、离散化、前缀和。
lower_bound/upper_bound 中分别怎样表达?Fenwick 真正擅长的不是“数组”,而是动态维护一个有序多重集的前缀计数。典型流程:
<rank、<=rank 或总数减前缀;add(rank,+1) 与 sum(rank) 中秩统一为 1-based。答案的配对数可达 $O(n^2)$ 或 $O(n^3)$,必须用 long long。离散化只保持比较顺序,不会保持差值;需要做加减阈值时,应在原始前缀和值上二分,而不是在秩上直接减。
< 与 <= 的边界简单;若允许重复,必须仍使用严格秩查询。离散化权值。左到右扫描:结构中存下标小于 $j$ 的值,left_greater[j]=(j-1)-count(<=a_j)。右到左扫描得到 right_smaller[j]=count(<a_j)。答案为两者乘积之和。
对固定 $j$,每个满足 $a_i>a_j$ 的左下标和每个满足 $a_k<a_j$ 的右下标能且只能组合成一个合法三元组;反之合法三元组必被其中点项计入。对全部 $j$ 求和覆盖且不重。
复杂度 $O(n\log n)$、空间 $O(n)$,能承受 $10^6$。
rank = coordinate_compress(a)
scan left -> right to compute left_greater[j]
clear Fenwick
scan right -> left to compute right_smaller[j]
answer = sum(left_greater[j] * right_smaller[j])
R_j 范围仅为 $1..n$,不需对原值做值域树。哈希/排序预处理左右频次。Fenwick 初始加入全部 $R_j$;从左到右,对当前位置先删除 $R_i$,再查询 <L_i 的剩余个数。结构中恰是所有 $j>i$,查询条件恰是 $R_j<L_i$,故每个目标对在其左端点处理时计一次。复杂度 $O(n\log n)$。
L = prefix_occurrence_number(a)
R = suffix_occurrence_number(a)
Fenwick.add(R[j], 1) for all j
for i from 1 to n:
Fenwick.add(R[i], -1)
answer += Fenwick.sum(L[i] - 1)
upper_bound。依次处理右端点 $r$。Fenwick 存所有此前前缀和;查询其中严格大于 $P_r-t$ 的数量,再加入 $P_r$。每个此前前缀唯一对应一个 $l-1$,不等式与子段条件等价,所以计数充要且不重复。复杂度 $O(n\log n)$,前缀和与答案均用 64 位。
prefix[0] = 0; prefix[i] = prefix[i-1] + a[i]
coordinates = sorted_unique(prefix)
Fenwick.add(rank(prefix[0]), 1)
for r in 1..n:
le = number of inserted prefixes <= prefix[r] - t
answer += inserted_count - le
Fenwick.add(rank(prefix[r]), 1)
给定 $1\le N\le2\times10^5$ 与 $1\le A_i\le N$。对每个满足 $P_i\le A_i$ 的 $1..N$ 排列 $P$,计算其逆序数;求所有合法排列逆序数之和,模 $10^9+7$。
c_x==1 的出现次数分组,只有两上界位于同一组时才能用前缀乘积相除;这一步避免了对 0 求模逆。先计算全部 $c_x$ 与 $T=\prod c_x$。对任意 $u\le v$,把某个上界 $v$ 改为 $u$:对值 $x\in(u,v]$,该位置不再可选,所以降序放置时选择数由 $c_x$ 变为 $c_x-1$;其他值不变。因此新排列数确为 $T R(u,v)$。
固定 $iP_j$,贡献为 $TR/2$。若 $A_i>A_j$,完全相同的对合证明“不逆序”数为 $TR/2$,故逆序数为 $T-TR/2$。这覆盖每个位置对且每个排列逆序恰按其位置对计一次。
为在 $O(N\log N)$ 汇总,令
$$ Qt=\prod{1\le x\le t,\ c_x\ne1}\frac{c_x-1}{c_x}. $$
在不跨越 c_x==1 的同一组内,$R(u,v)=Q_v/Q_u$。从左到右扫描当前上界 $v=A_j$,三个 Fenwick 分别维护此前上界的数量、$Q_u$ 之和与 $Q_u^{-1}$ 之和:
最后把正负修正乘 $T/2$。预处理与扫描均为 $O(N\log N)$,空间 $O(N)$,能通过 $N=2\times10^5$。
for x from N down to 1:
c[x] = count(A[i] >= x) - (N-x)
if c[x] <= 0: return 0
T = product(c[x])
build Q[x] from factors (c[x]-1)/c[x]
split value axis after every x with c[x] == 1
for v in A from left to right:
base_inversions += count_of_inserted_values_greater_than(v)
correction += Q[v] * sum(1/Q[u]) for inserted u <= v in same group
correction -= (1/Q[v]) * sum(Q[u]) for inserted u > v in same group
insert v into the three Fenwick trees
answer = T * base_inversions + T/2 * correction
完整 GNU C++17 参考实现:AtCoder-agc023_e.cpp
c_x 是整数选择数,不是模数意义下的值;必须先用整数判断是否为 0。c_x-1=0 外都可安全求逆;零因子用分组处理。二维偏序常由排序固定一维、Fenwick 维护另一维;若还多一维,可能进入 CDQ 分治。Fenwick 的限制是合并信息必须可做前缀差或单调选择,任意区间复杂结构仍需线段树。
upper_bound(P_r-t)?先用 $O(n^3)/O(n^2)$ 暴力对拍三题;每次写 Fenwick 前先用一句中文写明“结构中当前有哪些对象”。若这句随扫描位置不成立,代码通常也会错。