HDU5792(离散化+树状数组求逆序数)树状数组求逆序深入讲解

本来以为自己理解了树状数组求逆序的道理,今天做了一道题还是错了,现在终于搞懂,做个笔记防止遗忘!

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中重叠点的情况

L - World is Exploding
Time Limit:1000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u

Description

Given a sequence A with length n,count how many quadruple (a,b,c,d) satisfies:  .

Input

The input consists of multiple test cases. 
Each test case begin with an integer n in a single line. 

The next line contains n integers 
 

Output

For each test case,output a line contains an integer.

Sample Input

4
2 4 1 3
4
1 2 3 4

Sample Output

1
0
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> 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<int, int> 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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值