求逆序对(线段树版)

一个序列a1,a2,a3...aN,求出满足:ai > aj 且 i < j 的个数。

一个最容易想到的方法就是枚举所有的i,j看看是否满足,显然是O(n^2)的复杂度。不够好。

可以这样考虑,开一个数组保存这n个数出现的位置和对应的次数,这个数组要开到a数组里最大的那个数MAX,也就是hash,初始状态数组里没有元素,每个数对应的个数都是0.

如果考虑第i个数,找到比它大的所有的数 的个数,查找的范围即 ai+1~MAX,这就是到i这个位置的逆序对的总和,接着把a[i]这个数添加到数组里,也就是a[i]这个位置的数量加1。一直进行到n结束,逆序对就求了出来。

这样做得复杂度依然是O(n^2),但查找和增加的操作可用线段树解决,这样复杂度就降到了O(nlogn)。

还有一个问题,如果a[i]可以达到10^9甚至更大,数组都开不下,即便开的下,时间上也不能承受,这样就要用到离散化,将n个数映射到1~n的范围内,这个操作排序加二分可轻松解决。所有数控制在n 的范围内,线段树解决是非常理想的。

以POJ2299为例 : 题目就是要求逆序对。详见代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <set>
#include <stack>
#include <cctype>
#include <algorithm>
#define lson o<<1, l, m
#define rson o<<1|1, m+1, r
using namespace std;
typedef long long LL;
const int maxn = 500500;
const int MAX = 0x3f3f3f3f;
int n, a, b, in[maxn], tt[maxn], fu[maxn], f[maxn];
LL num[maxn<<2];
int bs(int v, int x, int y) {
    while(x < y) {
        int m = (x+y) >> 1;
        if(fu[m] >= v) y = m;
        else x = m+1;
    }
    return x;
}
void up(int o) {
    num[o] = num[o<<1] + num[o<<1|1];
}
void build(int o, int l, int r) {
    num[o] = 0;
    if(l == r) return ;
    int m = (l+r) >> 1;
    build(lson);
    build(rson);
}
void update(int o, int l, int r) {
    if(l == r) {
        num[o]++;
        return ;
    }
    int m = (l+r) >> 1;
    if(a <= m) update(lson);
    else update(rson);
    up(o);
}
LL query(int o, int l, int r) {
    if(a <= l && r <= b) return num[o];
    int m = (l+r) >> 1;
    LL ans = 0;
    if(a <= m) ans += query(lson);
    if(m < b ) ans += query(rson);
    return ans ;
}
int main()
{
    while(cin >> n, n) {
        for(int i = 0; i < n; i++) {
            scanf("%d", &in[i]);
            tt[i] = in[i];  //tt记录原序列
        }
        sort(in, in+n);
        int k = 0;
        fu[k++] = in[0];   //fu为辅助数组
        for(int i = 1; i < n; i++)
            if(in[i] != in[i-1]) fu[k++] = in[i];
        b = 0;
        for(int i = 0; i < n; i++) {  //离散过程,二分
            f[i] = bs(tt[i], 0, k-1);
            b = max(b, f[i]);
        }
        LL ans = 0;
        build(1, 0, b);
        for(int i = 0; i < n; i++) {
            a = f[i] + 1;    // 查询f[i]+1~n的个数,个数就是f[i]当前的逆序对总数
            ans += query(1, 0, b);
            a = f[i];  // 将f[i]添加到数组中
            update(1, 0, b);
        }
        cout << ans << endl;
    }
    return 0;
}

求逆序对(线段树版),布布扣,bubuko.com

时间: 2024-09-30 15:10:28

求逆序对(线段树版)的相关文章

求逆序对 (树状数组版)

基本思想和线段树求解逆序数是一样的,前一篇<求逆序对 线段树版>也介绍过,先对输入数组离散,数组里的元素都不相同可以直接hash,存在相同的数话可以采用二分. 离散化后对于每个f[i],找到f[i]+1~ n中的个数,也就是到i这个位置,一共有多少比f[i]大的数,统计之后在将f[i]的位置上的数量加1. 这样一来统计的就是类似a[i]~n的和,可以想象成 把树状数组反过来统计,即统计的时候加lowbit,更新的时候减lowbit. 还是 以POJ 2299为例. #include <i

Day2:T4求逆序对(树状数组+归并排序)

T4: 求逆序对 A[I]为前缀和 推导 (A[J]-A[I])/(J-I)>=M A[j]-A[I]>=M(J-I) A[J]-M*J>=A[I]-M*I 设B[]=A[]-M*(); B[J]>=B[I] 也就是求逆序对: 求逆序对的方法主要有两种: 归并排序: 树状数组: 这里两种方法都学习一下: 1.之前对于树状数组的印象就只有单点修改和区间求和 一直觉得lowbit是一个神奇的东西(至今没有搞懂原理) 上网搜了一下用树状数组求逆序对的方法,发现有一个大神写的很棒....看

逆序对 线段树&amp;树状数组

17年的时候在HDU新生赛的时候遇到这样一道题目, 当时对于这种题目, 只会n^2去数左边比他大的个数 再相加一下 就是答案了. 无奈n是1e5 毫无疑问的T了. 后来学长说这个不就是归并排序吗, 你去学一下归并就可以做了, 然后我去学了归并, 又交了一发, 结果竟然还是T(这Y的不是耍我玩吗). 然后从另一位学长哪里听说了用线段树去求逆序对, 把n^2变成nlogn就不会T了,最后, 我又学了线段树,终于这回AC了.写这个帖子的时候,我顺便去HDU找了找这道题目,结果找不到这道题目,竟然没挂出

归并求逆序数 &amp;&amp; 线段树求逆序数

Brainman Time Limit: 1000 MS Memory Limit: 30000 KB 64-bit integer IO format: %I64d , %I64u   Java class name: Main [Submit] [Status] [Discuss] Description Background Raymond Babbitt drives his brother Charlie mad. Recently Raymond counted 246 toothp

hdu 4911 求逆序对数+树状数组

http://acm.hdu.edu.cn/showproblem.php?pid=4911 给定一个序列,有k次机会交换相邻两个位置的数,问说最后序列的逆序对数最少为多少. 实际上每交换一次能且只能减少一个逆序对,所以问题转换成如何求逆序对数. 归并排序或者树状数组都可搞 树状数组: 先按大小排序后分别标号,然后就变成了求1~n的序列的逆序数,每个分别查询出比他小的用i减,在把他的值插入即可 #include <cstdio> #include <cstdlib> #includ

BZOJ 3295 CQOI 2011 动态逆序对 线段树套Treap

题目大意:给出一个数列,每次从这个序列中删掉一个数字,问每次删之前逆序对的数量是多少. 思路:这个题用CDQ分治是飞快的,然而我不知道怎么写..于是就朴素的写了树套树.然后就朴素的被卡常了 内层用一个线段树.这个线段树不修改,一开始就要建好,然后线段树的每一个节点维护一个平衡树,存的是线段树存的区间中所有的值. 一开始先算一下逆序对数,然后每次删点的时候,先查询在这个点之前有多少大于他的,后面有多少小于他的,总的逆序对中将这些减掉.这个过程通过树套树不难实现. CODE(交了这个代码T掉的,请选

loj #535. 「LibreOJ Round #6」花火 树状数组求逆序对+主席树二维数点+整体二分

$ \color{#0066ff}{ 题目描述 }$ 「Hanabi, hanabi--」 一听说祭典上没有烟火,Karen 一脸沮丧. 「有的哦-- 虽然比不上大型烟花就是了.」 还好 Shinobu 早有准备,Alice.Ayaya.Karen.Shinobu.Yoko 五人又能继续愉快地玩耍啦! 「噢--!不是有放上天的烟花嘛!」Karen 兴奋地喊道. 「啊等等--」Yoko 惊呼.Karen 手持点燃引信的烟花,「嗯??」 Yoko 最希望见到的是排列优美的烟火,当然不会放过这个机会-

hdu1394(线段树求逆序对)

题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 线段树功能:update:单点增减 query:区间求和 分析:如果是0到n-1的排列,那么如果把第一个数放到最后,对于这个数列,逆序数是减少a[i],而增加n-1-a[i]的,所以每次变化为res+=n-a[i]-1-a[i]. #include<iostream> #include<cstdio> #include<cstring> #include<alg

线段树求逆序对

嘘!这里是逆序对的题目链接 以前一直不知道线段树有求逆序对的功能 之前老师提了一下又刚好没听 今天自己模拟了一遍似乎是对了 代码虽短但耗费的空间却大 而归并排序代码虽然复杂却只耗费少量的空间 大概思想: 1.建一棵和最大数值一样大的线段树 2.每次在树中查找这个点的位置 3.在查找的过程中有两种选择 (1).往左子树下去 这时需要将当前节点的值减去左节点的值 (2).往右子树下去 不用操作 4.进入子树,直到遇到这个值所在的区间(l=r=a)的时候 5.将这个区间的值加1,如何维护树 1 #in