树状数组 && 线段树应用 -- 求逆序数

参考:算法学习(二)——树状数组求逆序数 、线段树或树状数组求逆序数(附例题)

应用树状数组 || 线段树求逆序数是一种很巧妙的技巧,这个技巧的关键在于如何把原来单纯的求区间和操作转换为 求小于等于a的数的总数 再转换为 求序列里大于a的数的总数,学习这个技巧源于一道题目 poj 3067 Japan (一道需要YY后运用这个技巧求解的题目),此外这个技巧也让我联想到 树状数组区间加/单点求值的技巧(基于区间加法的思维),话不多说,开始正题。

 

一、什么是逆序数?

  在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。一个排列中所有逆序总数叫做这个排列的逆序数。也就是说,对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。

  举个栗子:如2431中,21,43,41,31是逆序,逆序数是4。

 

二、如何用树状数组求逆序数?

1、定义: a[ i ] 储存原始序列(事实上只需要一个变量 a 就ok了), i 即 出现的顺序;c[ k ] 树状数组 k 为 a[ i ]。

2、步骤:按顺序输入储存原始序列 a[ i ] , 输入的同时维护 c[ i ] , a[i] 出现 c[ a[i] ] 则加一, add(a[ i ], 1);所以用数组数组求(1, a[i]) 的区间和的意义就变成了统计小于等于 a[ i ] 的数的个数, 若当前序列总数为 N, 则 N - sum( a ) 就是长度为N的序列中比 a 大的数字的总数了。

3、举个栗子:2431   ans = 0 + 0 + 1 + 3 = 4;   分别是(21, 43, 41, 31)。

ia[ i ]kc[ 1 ] ~ c[ 4 ] i - sum( k )
1220 1 0 01 - 1 = 0
2440 1 0 12 - 2 = 0
3330 1 1 13 - 2 = 1
4111 1 1 14 - 1 = 3

              

 

 

 

 

 

4、贴个代码:

 1 ///树状数组求逆序数
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <iostream>
 5 #include <algorithm>
 6 using namespace std;
 7 
 8 const int MAXN = 1001;
 9 int c[MAXN];
10 int N;
11 
12 int lowbit(int x) ///实现树状数组需要的基本函数
13 {
14     return x&(-x);
15 }
16 
17 void add(int i, int value)
18 {
19     while(i <= N)
20     {
21         c[i]+=value;
22         i+=lowbit(i);
23     }
24 }
25 
26 int sum(int i)
27 {
28     int res = 0;
29     while(i > 0)
30     {
31         res+=c[i];
32         i-=lowbit(i);
33     }
34     return res;
35 }
36 int main()
37 {
38     while(~scanf("%d", &N))
39     {
40         int ans = 0;
41         memset(c, 0, sizeof(c));
42         for(int i = 1; i <= N; i++)
43         {
44             int a;
45             scanf("%d", &a);
46             add(a, 1);
47             ans+=i-sum(a);
48         }
49         printf("%d\n", ans);
50     }
51     return 0;
52 }

 

三、如何用线段树求逆序数

与树状数组原理,只不过实现的区间求和的方式不同罢了

直接贴代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define L(a) a<<1
#define R(a) (a<<1)|1
const int maxn = 51000;
int ans[maxn];
struct node{
    int num,l,r;
}tree[maxn<<2];
int n;
void Build(int m,int l, int r){
    tree[m].l=l;
    tree[m].r=r;
    if(tree[m].l==tree[m].r){
        tree[m].num=0;
        return ;
    }
    int mid = (tree[m].l+tree[m].r)>>1;
    Build(L(m),l,mid);
    Build(R(m),mid+1,r); //并不要回溯, 建立空树
}
void Insert(int m,int l,int r,int x){
    if(tree[m].l==l&&tree[m].r==r){
        tree[m].num+=x; return ;
    }
    int mid = (tree[m].l+tree[m].r)>>1;
    if(r<=mid)
        Insert(L(m),l,r,x);
    else if(l>mid)
        Insert(R(m),l,r,x);
    else{
        Insert(L(m),l,mid,x);
        Insert(R(m),mid+1,r,x);
    }
    tree[m].num=tree[L(m)].num+tree[R(m)].num;
}
int Query(int m,int l,int r){
    if(tree[m].l==l&&tree[m].r==r)
        return tree[m].num;
    int mid = (tree[m].l+tree[m].r)>>1;
    if(r<=mid)
        return Query(L(m),l,r);
    if(l>mid)
        return Query(R(m),l,r);
    return Query(L(m),l,mid)+Query(R(m),mid+1,r);
}
int main(){
    int a,n,i,t;

        int k=0;
        scanf("%d",&n);
        memset(tree,0,sizeof(tree));
        Build(1,1,n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&ans[i]);
        }
        for(int i=1;i<=n;i++){
            Insert(1,ans[i],ans[i],1);// 每个位置插入1
            k+=(i - Query(1,1,ans[i]));
        }
        printf("%d\n",k);

    return 0;
}

 

 

四、几道题目

1、HDU 1394

思路:树状数组+暴力(每次a[ i ] 掉到最后的时候 减去比 a[ i ] 小的数, 加上比 a[ i ] 大的数)

Ac Code:

 1 ///HDU 1394 树状数组
 2 
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <iostream>
 6 #include <algorithm>
 7 #define INF 0x3f3f3f3f
 8 using namespace std;
 9 
10 const int MAXN = 5005;
11 
12 int N;
13 int c[MAXN], a[MAXN];
14 
15 int lowbit(int x)
16 {
17     return x&(-x);
18 }
19 
20 void add(int i, int value)
21 {
22     while(i <= N)
23     {
24         c[i]+=value;
25         i+=lowbit(i);
26     }
27 }
28 
29 int sum(int i)
30 {
31     int res = 0;
32     while(i > 0)
33     {
34         res+=c[i];
35         i-=lowbit(i);
36     }
37     //printf("%d\n" ,res);
38     return res;
39 }
40 
41 int main()
42 {
43     int cnt = 0;
44     while(~scanf("%d", &N))
45     {
46         cnt = 0;
47         memset(c, 0, sizeof(c));
48         memset(a, 0, sizeof(a));
49         for(int i = 1; i <= N; i++)
50         {
51             scanf("%d", &a[i]);
52             a[i]++;
53             add(a[i], 1);
54             cnt+=i-sum(a[i]);
55         }
56 
57         int ans = cnt;
58         //printf("%d\n", ans);
59         for(int i = 1; i <= N; i++)
60         {
61             a[i]--;
62             cnt = cnt-a[i]*2+N-1;
63             ans = min(ans, cnt);
64             //printf("%d\n", ans);
65         }
66         printf("%d\n", ans);
67     }
68     return 0;
69 }
View Code

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值