本来以为自己理解了树状数组求逆序的道理,今天做了一道题还是错了,现在终于搞懂,做个笔记防止遗忘!
1.我要求一个序列例如【1, 4, 4, 2, 7】(编号1-5)第2个数(即4)前面有几个小于它的,有几个大于它的,后面有几个小于它的,几个大于它的。或者在扩大一点范围,求有几个大于等于它的货小于等于它的。
方法如下:1:求A[3] 的前面有几个小于它的,设数组down_left[6] 存前面有几个小于此数的,注意是小于,不包含等于
for(int i = 1; i <= 5; i++)
{
down_left[i] = sum(A[i] - 1); // 求前面有几个数小于这个数
add(A[i], 1);
}
注意sum里面是A[i] - 1 而不是 A[i] ,如果是A[i] 那么结果是求前面有几个小于等于这个数
2.同理如果求前面有几个大于这个数,只需要用前面的总数 减去 前面小于等于这个数的数量(注意这里是减去小于等于)
for(int i = 1; i <= 5; i++)
{
up_left[i] = i - 1 - sum( A[i] ); // 求前面有几个数大于这个数,减去前面有几个小于等于这个数
add(A[i], 1);
}
3.如果求后面有几个小于(或者大于)这个数,那么只需从n->1遍历即可,其他同理1和2, 自己打印出来一对比就知道了
for(int i = 5; i >= 1; i--)
{
down_right[i] = sum(A[i] - 1); // 求前面有几个数小于这个数
add(A[i], 1);
}
#####################################
注意:数据大要离散化
#####################################
这儿再随便说两句离散化,由于树状数组在统计最大最小的时候要用到add(A[i], value)这个函数如果A[i]的值是非常大的话,是不能开那么大的bit[] 数组存储的,所以需要离散化(即
在保持原序列大小循序不变的情况下把数值的大小表示变得尽可能的小)
离散化,但数据范围太大是所借用的利器,举个例子,有四个数99999999 1 123 1583 数据范围太大,而树状数组中的c数组开的范围是数据的
范围,这时候就需要离散化,把四个数一次标号为1 2 3 4(即第一个数,第二个数。。。),按键值排序之后 依次为2 3 4 1(即从小到大排序
为第二个数,第三个数。。。),所以,第二个数是最小的,即f[2]=1,f[3]=2,f[4]=3,f[1]=4,也就是把键值变为了1~n,相对大小还是不变的,
即4 1 2 3。比如要求原来四个数的逆序数总和,现在就是求4 1 2 3的逆序数总和,大大节省了空间压力(树状数组的长度是数据范围)(这段话是
我学离散化的时候看到的最简洁的教学)
下面是AC的多校的最后一道题:用到了求正序,逆序,和离散化,还有什么高深的容斥原理(我感觉说白了就是去重,以后再学学吧),真是那句话,
艺多不压身,量变到质变!!
hdu5792,就是正序数*逆序数 - 4中重叠点的情况
Input
Output
Sample Input
4 2 4 1 3 4 1 2 3 4
Sample Output
1 0
#include
using namespace std;
typedef pair P;
typedef long long LL;
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define MAX_N 50005
#define LOCAL
int A[MAX_N]; //记录原数据和离散化后的值
int n;
int bit[MAX_N]; //由于数据范围为1e9,所以用树状数组处理逆序时需要离散化到MAX_N范围内
LL left_down[MAX_N], left_up[MAX_N]; //重复情况处理
LL right_down[MAX_N], right_up[MAX_N];
pair toindex[MAX_N];
void add(int i, int val)
{
for(; i <= n; i += i & (-i))
bit[i] += val;
}
int sum(int i)
{
int sum = 0;
for(; i >= 1; i -= i & (-i))
sum += bit[i];
return sum;
}
int main()
{
#ifdef LOCAL
freopen("b:\\data.in.txt", "r", stdin);
#endif
while(scanf("%d", &n) != EOF)
{
memset(bit, 0, sizeof(bit));
for(int i = 1; i <= n; i++)
{
scanf("%d", &A[i]);
toindex[i].first = A[i]; //存键值
toindex[i].second = i; //存坐标
}
sort(toindex+1, toindex+n+1); //按照键值升序排序
int cnt = 1;
A[toindex[1].second] = cnt;
for(int i = 2; i <= n; i++) //离散化,把数据相对大小顺序不变的数列的数据范围缩小到500000内
{
if(toindex[i].first!= toindex[i-1].first)
cnt++;
A[toindex[i].second] = cnt;
}
LL up_sum = 0, down_sum = 0;
for(int i = n; i >= 1; i--) // 求逆序和
{
up_sum += (n - i) - sum(A[i]); //求A[i]后面有多少个大于于(也就是后面的总数 - 后面等于小于它的数量)
down_sum += sum(A[i] - 1); //求A[i]后面有多少个小于它的,即逆序对
add(A[i], 1);
}
memset(bit, 0, sizeof(bit)); //左大和左小的数量
for(int i = 1; i <= n; i++)
{
left_down[i] = sum(A[i]-1);
left_up[i] = i - 1 - sum(A[i]);
cout << left_down[i] << " ";
add(A[i], 1);
}
memset(bit, 0, sizeof(bit)); //右边大,右边小的情况
for(int i = n; i >= 1; i--)
{
right_down[i] = sum(A[i] - 1);
right_up[i] = (n - i) - sum(A[i]);
// cout << right_up[i] << " ";
add(A[i], 1);
}
cout << endl;
LL ans = up_sum * down_sum;
for(int i = 1; i <= n; i++)
{
ans -= left_down[i] * right_down[i]; //四个可能的共同点
ans -= left_up[i] * right_up[i];
ans -= right_up[i] * right_down[i];
ans -= left_up[i] * left_down[i];
}
cout << ans << endl;
}
return 0;
}