逆序对的定义:长度为n的数组a,求满足i<j时a[i]>a[j]条件的数对个数。

第一次接触这种问题的人可能是更先想到的是n^2去暴力数前面有几个比他大的数。

 int main()
{
int n;
while(~scanf("%d", &n), n) {
ans = ;
for(int i = ; i <= n; i++)
scanf("%d", &a[i]);
for(int i = ; i <= n; i+=)
for(int j = ; j < i; j+=)
if(a[j] > a[i]) ans++;
printf("%d", ans);
}
return ;
}

n^2算法就是数一下前面有多少个数比现在这个数大 这样全部跑完只后就是逆序数了。

其中重点是 前面有多少个数比现在这个数大

但是每次从1 fo r一遍到i的位置太浪费时间了

所以我们可以用线段树来优化这个数数过程

 #include<bits/stdc++.h>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define LL long long
const int N=;
int sum[N<<], a[N];
LL ans;
void Update(int c, int l, int r,int rt) {
if(l == r) {
sum[rt]++;
return;
}
int m = l+r >> ;
if(c <= m) Update(c,lson);
else Update(c,rson);
sum[rt]=sum[rt<<]+sum[rt<<|];
}
LL Query(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R)
return sum[rt];
int m = l+r >> ;
LL cnt = ;
if(L <= m) cnt+=Query(L,R,lson);
if(m < R) cnt += Query(L,R,rson);
return cnt;
}
int main() {
int n;
while(~scanf("%d", &n), n){
ans = ;
memset(sum, , sizeof(sum));
for(int i = ; i <= n; i++){
scanf("%d", &a[i]);
}
for(int i = ; i <= n; i++) {
ans += Query(a[i],n,,n,);
Update(a[i],,n,);
}
printf("%d\n", ans);
}
return ;
}

线段树算法的在求逆序对的关键就是将出现过的数对应的位置标记一下(+1)

假设 i=k时, 查询一下区间  [a[k], n] 的区间和, 这个和就是(j < k && a[j] > a[k])  的数目

然后在a[k] 的位置 +1

重复这个过程就能求出解了

是不是很疑惑为什么?

当查询区间的时候, 如果在后面的区间内查询到次数不为0时, 说明有几个比他大数在他前面出现过,

重点就是标记。

这里还可以用树状数组来代替线段树。树状数组的特点就是好写,并且速度比线段树快一点。

 #include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define Fopen freopen("_in.txt","r",stdin); freopen("_out.txt","w",stdout);
#define LL long long
#define ULL unsigned LL
#define fi first
#define se second
#define pb push_back
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))amespace std;
const int N = +;
int tree[N], cnt = , A[N];
pair<int, int> P[N];
int lowbit(int x){
return x&(-x);
}
void Add(int x) {
while(x <= cnt) {
tree[x]++;
x += lowbit(x);
}
}
int Query(int x) {
int ret = ;
while(x) {
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
int main(){
int n;
while(~scanf("%d", &n), n) {
cnt = ;
for(int i = ; i <= n; i++) {
scanf("%d", &A[i]);
}
memset(tree, , sizeof(tree));
LL ans = ;
for(int i = ; i <= n; i++) {
ans += Query(cnt) - Query(A[i]);
Add(A[i]);
}
printf("%I64d\n", ans);
}
return ;
}

注意的是 线段树与树状数组求逆序对的时候 数值不能太大 比如在 a[i] <= 1e9的时候 就不能直接用树状数组和逆序数去求了,因为开不了那么大的空间。

但在这个时候,如果n不是很大可以先对数据进行离散化再进行使用树状数组或者线段树处理数据。

POJ-2299 Ultra-QuickSort

题意就是求逆序对, 但是这个a[i]的范围太大,所以我们不能直接进行求解,但是这个地方只于数的相对大小有关,对于数的差值无关,所以我们可以先对所有数离散化,再进行上述的操作。

 #include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
#define Fopen freopen("_in.txt","r",stdin); freopen("_out.txt","w",stdout);
#define LL long long
#define ULL unsigned LL
#define fi first
#define se second
#define pb push_back
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))amespace std;
const int N = +;
int tree[N], cnt = , A[N];
pair<int, int> P[N];
int lowbit(int x){
return x&(-x);
}
void Add(int x) {
while(x <= cnt) {
tree[x]++;
x += lowbit(x);
}
}
int Query(int x) {
int ret = ;
while(x) {
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
int main(){
int n;
while(~scanf("%d", &n), n) {
cnt = ;
for(int i = ; i <= n; i++) {
scanf("%d", &A[i]);
P[i].fi = A[i];
P[i].se = i;
}
sort(P+, P++n);
P[].fi = -;
for(int i = ; i <= n; i++) {
if(P[i-].fi == P[i].fi)
A[P[i].se] = cnt;
else cnt++, A[P[i].se] = cnt;
}
memset(tree, , sizeof(tree));
LL ans = ;
for(int i = ; i <= n; i++) {
ans += Query(cnt) - Query(A[i]);
Add(A[i]);
}
printf("%I64d\n", ans);
}
return ;
}

POJ-2299

Emmm, 上面的都是去年写的,最近突然发现写逆序对不需要那么麻烦, 所以我就再新加一个做法,本来是想删掉的,后来想想各有优点,并且,上面那个博客是最第一个写的这么详细的博客,本来是当时自己太捞了,写给自己看的,2333。

重温一下逆序对的定义, 就是对于每一个数都求出在他前面有多少个数比他大,然后对每一个数的这个东西求和,最后的值就是逆序对数了。

第二种做法就是:讲元素从大到小sort,如果相同的话,位置大的排前面,然后按照这个顺序查询前面的位置有多少个位置的数出现过,然后加到答案里, 然后再标记一下这个位置,以便下次询问。

由于我们是向前询问多少个数出现过了, 并且逆序对的定义是不包括相等的数的,所以我们要前处理位置再后面的数,再处理在前面的数。

原理就是,先出现的数必然大,所以在你前面标记过位置的数就一定大于当前的数。

还是上面那个题目,这个时候我们就不需要离散化了。

 #include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define Fopen freopen("_in.txt","r",stdin); freopen("_out.txt","w",stdout);
#define LL long long
#define ULL unsigned LL
#define fi first
#define se second
#define pb push_back
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))amespace std;
const int N = +;
int tree[N], n;
struct Node{
int a;
int id;
}A[N];
bool cmp(Node x1, Node x2){
if(x1.a == x2.a) return x1.id > x2.id;
return x1.a > x2.a;
}
int lowbit(int x){
return x&(-x);
}
void Add(int x) {
while(x <= n) {
tree[x]++;
x += lowbit(x);
}
}
int Query(int x) {
int ret = ;
while(x) {
ret += tree[x];
x -= lowbit(x);
}
return ret;
}
int main(){
while(~scanf("%d", &n), n) {
for(int i = ; i <= n; i++) {
scanf("%d", &A[i].a);
A[i].id = i;
}
memset(tree, , sizeof(tree));
LL ans = ;
sort(A+, A++n, cmp);
for(int i = ; i <= n; i++) {
ans += Query(A[i].id);
Add(A[i].id);
}
printf("%I64d\n", ans);
}
return ;
}

POJ-2299(2)

结论是2个方法都有优点吧, 如果相同元素的个数多的话,离散化或者排序之后n的个数很小,那么前面那种或许会更优, 如果n不大,a[i]的值很大,我们就可以用第二种写法去解决。

当然有些题目只能第二种写法求解如:

二维树状数组的逆序数。  往里拐一下。 传送门

逆序对 线段树&树状数组 (重制版)的更多相关文章

  1. BZOJ_3295_[Cqoi2011]动态逆序对_CDQ分治+树状数组

    BZOJ_3295_[Cqoi2011]动态逆序对_CDQ分治+树状数组 Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一 ...

  2. [BZOJ3295][Cqoi2011]动态逆序对 CDQ分治&树套树

    3295: [Cqoi2011]动态逆序对 Time Limit: 10 Sec  Memory Limit: 128 MB Description 对于序列A,它的逆序对数定义为满足i<j,且 ...

  3. HDU 6318.Swaps and Inversions-求逆序对-线段树 or 归并排序 or 离散化+树状数组 (2018 Multi-University Training Contest 2 1010)

    6318.Swaps and Inversions 这个题就是找逆序对,然后逆序对数*min(x,y)就可以了. 官方题解:注意到逆序对=交换相邻需要交换的次数,那么输出 逆序对个数 即可. 求逆序对 ...

  4. 求逆序对 ----归并排 & 树状数组

    网上看了一些归并排求逆序对的文章,又看了一些树状数组的,觉得自己也写一篇试试看吧,然后本文大体也就讲个思路(没有例题),但是还是会有个程序框架的 好了下面是正文 归并排求逆序对 树状数组求逆序对 一. ...

  5. bzoj3295: [Cqoi2011]动态逆序对(cdq分治+树状数组)

    3295: [Cqoi2011]动态逆序对 题目:传送门 题解: 刚学完cdq分治,想起来之前有一道是树套树的题目可以用cdq分治来做...尝试一波 还是太弱了...想到了要做两次cdq...然后伏地 ...

  6. codevs 4163 求逆序对的数目 -树状数组法

    4163 hzwer与逆序对  时间限制: 10 s  空间限制: 256000 KB  题目等级 : 黄金 Gold 题目描述 Description hzwer在研究逆序对. 对于数列{a},如果 ...

  7. 2019.01.22 bzoj3333: 排队计划(逆序对+线段树)

    传送门 题意简述:给出一个序列,支持把ppp~nnn中所有小于等于apa_pap​的'扯出来排序之后再放回去,要求动态维护全局逆序对. 思路:我们令fif_ifi​表示第iii个位置之后比它大的数的个 ...

  8. 【bzoj3295】[Cqoi2011]动态逆序对 线段树套SBT

    题目描述 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序 ...

  9. 【a703】求逆序对(线段树的解法)

    Time Limit: 10 second Memory Limit: 2 MB 问题描述 给定一个序列a1,a2...an.如果存在i小于j 并且ai大于aj,那么我们称之为逆序对,求给定序列中逆序 ...

随机推荐

  1. java遍历所有目录和文件

    package xian; import java.io.File; import java.util.ArrayList; public class GetFile { private static ...

  2. C# 10分钟完成百度语音技术(语音识别与合成)——入门篇

    我们已经讲了人脸识别(入门+进阶).图片识别(入门).下面是链接: C# 10分钟完成百度人脸识别——入门篇 C# 30分钟完成百度人脸识别——进阶篇(文末附源码) C# 10分钟完成百度图片提取文字 ...

  3. bucket list 函数解析

    cls_bucket_list 函数 librados::IoCtx index_ctx; // key   - oid (for different shards if there is any) ...

  4. C语言编程学习打造——做题游戏

    C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...

  5. java常见面试题目(二)

    部分没有答案可以自行百度. 1.myeclipse与eclipse的区别. 2.说说对maven或者SVN的理解. 3.类的加载过程 (创建对象的过程)  1)子父类里静态属性 赋上默认初始值 如果有 ...

  6. go 学习笔记之有意思的变量和不安分的常量

    首先希望学习 Go 语言的爱好者至少拥有其他语言的编程经验,如果是完全零基础的小白用户,本教程可能并不适合阅读或尝试阅读看看,系列笔记的目标是站在其他语言的角度学习新的语言,理解 Go 语言,进而写出 ...

  7. 一个有趣的 5 X 5 方阵一笔画问题

      这个问题是在我上小学时同学告诉我的,当时觉得好玩,就随便瞎画这玩儿,不过从小学到大学,没有一次画成功过.这个问题起初同学告诉我的时候,图不是这样画的,我只是为了好表达,将问题抽象成网格了,原问题是 ...

  8. 洛谷 P1177 【模板】快速排序

    这道题用传统快排(如下所示)的结果就是最后三个点TLE: void swap(int &a, int &b) { int tmp = a; a = b; b = tmp; } void ...

  9. vue中的v-if和v-show的区别

    v-if和v-show的区别是前端面试中常问的基础知识点,v-if.v-show顾名思义就是用来判断视图层展示效果的.那么具体是怎么展示呢?v-if和v-show的区别又是什么呢? 首先我们可以来看一 ...

  10. 2018年蓝桥杯b组国赛真题

    1.标题:换零钞x星球的钞票的面额只有:100元,5元,2元,1元,共4种.小明去x星旅游,他手里只有2张100元的x星币,太不方便,恰好路过x星银行就去换零钱.小明有点强迫症,他坚持要求200元换出 ...