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

应用树状数组 || 线段树求逆序数是一种很巧妙的技巧,这个技巧的关键在于如何把原来单纯的求区间和操作转换为 求小于等于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)。

i a[ i ] k c[ 1 ] ~ c[ 4 ]  i - sum( k )
1 2 2 0 1 0 0 1 - 1 = 0
2 4 4 0 1 0 1 2 - 2 = 0
3 3 3 0 1 1 1 3 - 2 = 1
4 1 1 1 1 1 1 4 - 1 = 3

4、贴个代码:

 ///树状数组求逆序数
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std; const int MAXN = ;
int c[MAXN];
int N; int lowbit(int x) ///实现树状数组需要的基本函数
{
return x&(-x);
} void add(int i, int value)
{
while(i <= N)
{
c[i]+=value;
i+=lowbit(i);
}
} int sum(int i)
{
int res = ;
while(i > )
{
res+=c[i];
i-=lowbit(i);
}
return res;
}
int main()
{
while(~scanf("%d", &N))
{
int ans = ;
memset(c, , sizeof(c));
for(int i = ; i <= N; i++)
{
int a;
scanf("%d", &a);
add(a, );
ans+=i-sum(a);
}
printf("%d\n", ans);
}
return ;
}

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

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

直接贴代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define L(a) a<<1
#define R(a) (a<<1)|1
const int maxn = ;
int ans[maxn];
struct node{
int num,l,r;
}tree[maxn<<];
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=;
return ;
}
int mid = (tree[m].l+tree[m].r)>>;
Build(L(m),l,mid);
Build(R(m),mid+,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)>>;
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+,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)>>;
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+,r);
}
int main(){
int a,n,i,t; int k=;
scanf("%d",&n);
memset(tree,,sizeof(tree));
Build(,,n);
for(int i=;i<=n;i++)
{
scanf("%d",&ans[i]);
}
for(int i=;i<=n;i++){
Insert(,ans[i],ans[i],);// 每个位置插入1
k+=(i - Query(,,ans[i]));
}
printf("%d\n",k); return ;
}

四、几道题目

1、HDU 1394

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

Ac Code:

 ///HDU 1394 树状数组

 #include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std; const int MAXN = ; int N;
int c[MAXN], a[MAXN]; int lowbit(int x)
{
return x&(-x);
} void add(int i, int value)
{
while(i <= N)
{
c[i]+=value;
i+=lowbit(i);
}
} int sum(int i)
{
int res = ;
while(i > )
{
res+=c[i];
i-=lowbit(i);
}
//printf("%d\n" ,res);
return res;
} int main()
{
int cnt = ;
while(~scanf("%d", &N))
{
cnt = ;
memset(c, , sizeof(c));
memset(a, , sizeof(a));
for(int i = ; i <= N; i++)
{
scanf("%d", &a[i]);
a[i]++;
add(a[i], );
cnt+=i-sum(a[i]);
} int ans = cnt;
//printf("%d\n", ans);
for(int i = ; i <= N; i++)
{
a[i]--;
cnt = cnt-a[i]*+N-;
ans = min(ans, cnt);
//printf("%d\n", ans);
}
printf("%d\n", ans);
}
return ;
}

树状数组 && 线段树应用 -- 求逆序数的更多相关文章

  1. hdu1394(枚举/树状数组/线段树单点更新&区间求和)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 题意:给出一个循环数组,求其逆序对最少为多少: 思路:对于逆序对: 交换两个相邻数,逆序数 +1 ...

  2. 洛谷P2414 阿狸的打字机 [NOI2011] AC自动机+树状数组/线段树

    正解:AC自动机+树状数组/线段树 解题报告: 传送门! 这道题,首先想到暴力思路还是不难的,首先看到y有那么多个,菜鸡如我还不怎么会可持久化之类的,那就直接排个序什么的然后按顺序做就好,这样听说有7 ...

  3. hdu 1166:敌兵布阵(树状数组 / 线段树,入门练习题)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  4. hdu 5147 Sequence II【树状数组/线段树】

    Sequence IITime Limit: 5000/2500 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Problem ...

  5. 【poj2182】【poj2828】树状数组/线段树经典模型:逆序查找-空位插入法

    poj2182题意:有一个1~n的排列,现在给定每个人前面有多少个人的编号比他大,求这个排列是什么.n<=8000 poj2182题解: 逆序做,可以确定二分最后一个是什么,然后删除这个数.树状 ...

  6. BZOJ 3333 排队计划 树状数组+线段树

    题目大意:给定一个序列.每次选择一个位置,把这个位置之后全部小于等于这个数的数抽出来,排序,再插回去,求每次操作后的逆序对数 首先我们每一次操作 对于这个位置前面的数 因为排序的数与前面的数位置关系不 ...

  7. 【BZOJ3333】排队计划 树状数组+线段树

    [BZOJ3333]排队计划 Description Input Output Sample Input 6 2 160 163 164 161 167 160 2 3 Sample Output 6 ...

  8. BZOJ_1901_&_ZJU_2112_Dynamic_Rankings_(主席树+树状数组/线段树+(Treap/Splay))

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1901 给出一个长度为n的数列A,有m次询问,询问分两种:1.修改某一位置的值;2.求区间[l, ...

  9. 第十四个目标(dp + 树状数组 + 线段树)

    Problem 2236 第十四个目标 Accept: 17    Submit: 35 Time Limit: 1000 mSec    Memory Limit : 32768 KB  Probl ...

随机推荐

  1. (转)通过shell脚本实现批量添加用户和设置随机密码以及生产环境如何批量添加

    通过shell脚本实现批量添加用户和设置随机密码以及生产环境如何批量添加 原文:http://www.21yunwei.com/archives/4773 有一个朋友问我如何批量创建用户和设置密码 , ...

  2. 【计算机网络】详解HttpURLConnection

    请求响应流程 设置连接参数的方法 setAllowUserInteraction setDoInput setDoOutput setIfModifiedSince setUseCaches setD ...

  3. JQuery选择器——《锋利的JQuery》

    刚学CSS的时候我们已经接触了选择器,其实就是按照一定的规则选择出来我们想要获取到的元素.在这里,既然选择了用jQuery选择器,首先来谈谈JQuery选择器的优势: 1.简洁的写法:$()函数在很多 ...

  4. sqlite3在别的目录写文件的问题

    今天碰到一个文件,就是sqlite数据不能把db创建在别的目录下.找了好久不得其解.后来换了一个sqlite jar包就好了. 原来我用的是sqlite-nested 内嵌的jar包. 换成这里的包h ...

  5. bzoj 5340: [Ctsc2018]假面

    Description 题面 Solution 生命值范围比较小,首先维护每一个人在每个血量的概率,从而算出生存的概率,设为 \(a[i]\) 询问时,只需要考虑生存的人数,可以 \(DP\) 设 \ ...

  6. Git使用教程,感觉比较全,所以【转载】

    一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. 二:SVN与Git的最主要的区别? SVN是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,所以 ...

  7. intent 活动之间穿梭

    1.从当前activity,跳转到当前应用程序的activity Intent intent = new Intent(MainActivity.this, Intent2Activity.class ...

  8. C# ADO.NET面向对象想法

    我认为的面向对象就是把各种问题拆分开来 逐一解决,  我想的是先是数据库,到底有什么, 然后新建一个类,类里面先是private的私有的,但是可以有无数个可以连接private的pubilc的属性 可 ...

  9. 重构指南 - 使用多态代替条件判断(Replace conditional with Polymorphism)

    多态(polymorphism)是面向对象的重要特性,简单可理解为:一个接口,多种实现. 当你的代码中存在通过不同的类型执行不同的操作,包含大量if else或者switch语句时,就可以考虑进行重构 ...

  10. Dozer 实现对象间属性复制

    使用场景:两个领域之间对象转换. 比如:在系统分层解耦过程中, 对外facade接口,一般使用VO对象,而内core业务逻辑层或者数据层通常使用Entity实体. VO对象 package com.m ...