逆序对的定义:长度为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. 二进制文件安装安装etcd

    利用二进制文件安装安装etcd etcd组件作为一个高可用强一致性的服务发现存储仓库. etcd作为一个受到ZooKeeper与doozer启发而催生的项目,除了拥有与之类似的功能外,更专注于以下四点 ...

  2. Apache ActiveMQ 实践 <一>

    一.下载最新版本 ActiveMq http://activemq.apache.org/activemq-5152-release.html,下载目录如下: 二.创建项目 1.普通项目 添加 jar ...

  3. netty使用EmbeddedChannel对channel的出入站进行单元测试

    一种特殊的Channel实现----EmbeddedChannel,它是Netty专门为改进针对ChannelHandler的单元测试而提供的. 名称 职责 writeInbound 将入站消息写到E ...

  4. javascript基础入门知识点整理

    学习目标: - 掌握编程的基本思维 - 掌握编程的基本语法 typora-copy-images-to: media JavaScript基础 HTML和CSS 京东 课前娱乐 众人皆笑我疯癫,我笑尔 ...

  5. solidity智能合约字节数最大值及缩减字节数

    智能合约最大字节数 在Solidity中,EIP 170将contract的最大大小限制为24 KB .因此,如果智能合约内容过多,会导致无法进行发布操作. 减少压缩字节数方法 方法及变量命名 在一定 ...

  6. Spark 系列(三)—— 弹性式数据集RDDs

    一.RDD简介 RDD 全称为 Resilient Distributed Datasets,是 Spark 最基本的数据抽象,它是只读的.分区记录的集合,支持并行操作,可以由外部数据集或其他 RDD ...

  7. java学习中碰到的疑惑和解答(二)

    路径问题是一个在平时学习和开发碰到的常见问题,对于初学者是一个比较值得研究的东西.因此对路径问题进行总结. 1. 编写路径为了告诉编译器如何找到其他资源.   2. 路径分类: 相对路径:从当前资源出 ...

  8. Go中的命名规范

    1.命名规范 1.1 Go是一门区分大小写的语言. 命名规则涉及变量.常量.全局函数.结构.接口.方法等的命名. Go语言从语法层面进行了以下限定:任何需要对外暴露的名字必须以大写字母开头,不需要对外 ...

  9. AVL树(查找、插入、删除)——C语言

    AVL树 平衡二叉查找树(Self-balancing binary search tree)又被称为AVL树(AVL树是根据它的发明者G. M. Adelson-Velskii和E. M. Land ...

  10. git:将代码提交到远程仓库(码云)

    初始化 进入一个任意的文件夹(如D:\aqin_test1\) git init # 初始化,让git将这个文件夹管理起来 git add . # 收集此文件夹下的所有文件 git config -- ...