先给出权值线段树的概念:
对一个数组 a a a 构造一个数组 b b b,其 b j b_j bj 表示 a a a 中 i i i 出现的次数,用 b b b 建立的线段树就是权值线段树
不那么通俗的理解:
在普通的线段树中一个节点通常对应一段区间,而在权值线段树中一个节点通常对应一段值域。
在含有 n n n 个整数的序列 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an 中,三个数被称作 thair
当且仅当 i < j < k i
求一个序列中 thair
的个数。
很容易发现我们可以枚举 j j j,算出可行的 i i i 的个数,再算出可行的 k k k 的个数,根据乘法原理 i × k i \times k i×k 就是以 j j j 为中点的 thair
个数(原题数据很水 O ( n 2 ) O(n^2) O(n2) 暴力能过QAQ)。
而根据权值线段树,比 j j j 小的就是 1 1 1 到 a i − 1 a_i-1 ai−1 的数的个数,比 j j j 大的就是 a i + 1 a_i+1 ai+1 到 1 0 5 10^5 105 的数的个数,而更新 a i a_i ai 个数只需要 a i a_i ai 到 a i a_i ai 加 1 1 1 即可,但 k k k 还要求下标必须大于 j j j 的下标,所以只需要再倒着跑一遍线段树即可。
#include
#define int long long
using namespace std;
const int N = 2 * 1e5 + 5;
int n, m, a[N], tree[N << 2], les[N], great[N], cnt[N];
void push_up(int cur){
tree[cur] = tree[cur * 2] + tree[cur * 2 + 1];
}
void build(int cur, int l, int r){
if(l == r){
tree[cur] = 0;
return ;
}
int mid = (l + r) / 2;
build(cur * 2, l, mid);
build(cur * 2 + 1, mid + 1, r);
push_up(cur);
}
void update(int cur, int l, int r, int qx, int qy, int val){
if(qx > r || qy < l){
return ;
}if(qx <= l && r <= qy){
tree[cur] ++;
return ;
}
int mid = (l + r) / 2;
update(cur * 2, l, mid, qx, qy, val);
update(cur * 2 + 1, mid + 1, r, qx, qy, val);
push_up(cur);
}
int query(int cur, int l, int r, int qx, int qy){
if(qx > r || qy < l){
return 0;
}if(qx <= l && r <= qy){
return tree[cur];
}
int mid = (l + r) / 2;
return query(cur * 2, l, mid, qx, qy) + query(cur * 2 + 1, mid + 1, r, qx, qy);
}
//上面全是线段树模板QAQ
signed main(){
cin >> n;
for(int i = 1; i <= n; i ++){
cin >> a[i];
}
build(1, 1, 1e5);//第一遍线段树
for(int i = 1; i <= n; i ++){//由于之前加进去的数肯定比 a[i] 下标小,所以正序即可
les[i] = query(1, 1, 1e5, 1, a[i] - 1);//统计比 a[i] 小的数的个数
update(1, 1, 1e5, a[i], a[i], 1);//将 a[i] 计入个数
}
build(1, 1, 1e5);//第二遍线段树
int ans = 0;
for(int i = n; i >= 1; i --){
great[i] = query(1, 1, 1e5, a[i] + 1, 1e5);//统计比 a[i] 大的数的个数
update(1, 1, 1e5, a[i], a[i], 1);//还是将 a[i] 计入个数
ans += les[i] * great[i];//统计答案
}
cout << ans;
return 0;
}
完结撒花!!!