二分偏序问题通常以 i > ( < , ≥ , ≤ ) j , u i > ( < , ≥ , ≤ ) v j i>(<,\ge,\le)j,u_i>(<,\ge,\le)v_j i>(<,≥,≤)j,ui>(<,≥,≤)vj等形式出现,求符合以上两种情况的有序对的个数,朴素来讲这种题目可以用 O ( n 2 ) O(n^2) O(n2)的复杂度来解决,但可以用树状数组来优化,使其复杂度为 O ( n log n ) O(n\log n) O(nlogn)
典型的二分偏序问题,逆序对
P1980 逆序对
首先按照两个条件中的一个为第一关键字排序(此处下标已经排序过了),之后从左到右进行枚举,用桶的方式来记录某个数字的出现,对枚举到的数字
a
i
a_i
ai,用树状数组查询之前枚举了的小于等于
a
i
a_i
ai的数的个数,
i
−
q
u
e
r
y
(
a
i
)
i-query(a_i)
i−query(ai)即为前面的数与
a
i
a_i
ai构成的逆序对的个数,最后由于数字比较大,用离散化处理一下便可以了。
ac代码:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e5 + 5;
int tree[N], b[N], a[N];
int n;
long long ans;
int lowerbit(int x)
{
return (x & (-x));
}
int query(int r)
{
int res = 0;
while (r)
{
res += tree[r];
r -= lowerbit(r);
}
return res;
}
void update(int i, int x)
{
int r = i;
while (r <= n)
{
tree[r] += x;
r += lowerbit(r);
}
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + n + 1);
int index;
for (int i = 1; i <= n; i++)
{
index = lower_bound(b + 1, b + n + 1, a[i]) - b;//利用了二分查找来找离散化数字
ans += i - 1 - query(index);
update(index, 1);
}
cout << ans << '\n';
return 0;
}
此处可以优化用一个rank数组来记录做到
O
(
1
)
O(1)
O(1)查询离散化数字,具体做法如下:
离散化模板:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e5 + 5;
struct node
{
int num;
int val;
bool operator<(const node &b) const
{
if (val == b.val)
return num < b.num;
return val < b.val;
}
} a[N];
int rk[N];
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
a[i].num = i;
cin >> a[i].val;
}
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i++)
{
rk[a[i].num] = i; //离散化匹配
}
return 0;
}