树状数组
树状数组又是一个区间查询修改利器 前缀和的维护 差分的维护
又强又方便
二进制组合原理
int n, m;
ll arr[MAXN] = {0}, crr[MAXN] = {0}; //arr原数组, crr二进制下标树
int lowbit(int x)
{
return x & -x;
}
void add(int pos, int val)
{
for( ; pos <= n; pos += lowbit(pos))
crr[pos] += val;
}
ll ask(int pos)
{
ll ret = 0;
for( ; pos; pos -= lowbit(pos))
ret += crr[pos];
return ret;
}
用法 1,区间查询和 [fst, lst]
前缀和相减即可 sum = crr[x] - crr[y - 1]
用法2 单点更新 add(pos, val)
用法3 区间加和 [fst, lst] add(fst, val), add(lst + 1, -val); 差分
用法4 区间加和的单点查询 ans = ask(k) + arr[k];
树状数组求逆序数
树状数组可以求前缀和,
那么设一个数组C记录当前下标的数的个数
所以从后往前扫数组可以logn的查询出当前值之后 的 比当前值小的所有数的前缀和(逆序数)
扫完再把当前值插入二进制下标树即可
当值太大的时候可以离散 这时候时间复杂度肯定就比归并要大的多了, 不过可以解决归并背不下来的情况
/*
Zeolim - An AC a day keeps the bug away
*/
//#pragma GCC optimize(2)
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <string>
#include <cstring>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
#include <sstream>
#include <map>
#include <ctime>
#include <vector>
#include <fstream>
#include <list>
#include <iomanip>
#include <numeric>
using namespace std;
typedef long long ll;
typedef long double ld;
const int INF = 0x3f3f3f3f;
const ld PI = acos(-1.0);
const ld E = exp(1.0);
const int MAXN = 1e6 + 10;
ll arr[MAXN] = {0};
ll brr[MAXN] = {0};
ll c[MAXN] = {0};
ll rp(ll x, ll n)
{
return lower_bound(brr, brr + n, x) - brr + 1;
}
ll lobit(ll x)
{
return x & -x;
}
void add(ll x, ll n)
{
for(; x <= n; x += lobit(x))
++c[x];
}
ll ask(ll x)
{
ll ret = 0;
for( ; x; x -= lobit(x))
ret += c[x];
return ret;
}
int main()
{
//ios::sync_with_stdio(false);
//cin.tie(0); cout.tie(0);
//freopen("D://test.in", "r", stdin);
//freopen("D://test.out", "w", stdout);
int n;
cin >> n;
for(int i = 0; i < n; ++i)
{
cin >> arr[i];
}
memcpy(brr, arr, sizeof(arr));
sort(brr, brr + n);
int ln = unique(brr, brr + n) - brr;
ll ans = 0;
for(int i = n - 1; i >= 0; --i)
{
int pos = rp(arr[i], ln);
ans += ask(pos - 1);
add(pos, ln);
}
cout << ans << '\n';
return 0;
}