zoukankan      html  css  js  c++  java
  • 浅谈树状数组求逆序对及离散化的几种方式及应用

    一、树状数组求逆序对的原理

    1.问题描述:假设当前有一个数列a,求数列中逆序对数,即数字较小的数位置较数字较大的数靠后的有序对的个数。

    那么有什么解法呢?

    (1)O(N^2)暴力比对,TLE。

    (2)归并排序求逆序对(在此先不提);

    (3)树状数组求逆序对。

    2.树状数组求逆序对的原理

    我们知道,树状数组是可以做到单点修改,区间求和的,那我们不妨以数字为下标,每来一个新的数就让他对应数字为下标的数增加一,代表下标在当前已处理的数字中出现的次数;

    方案一:那么我们将数列倒序做一遍树状数组,那么当前数字的前一个数的前缀和即为以该数为较大数的逆序对的个数。

    因为我们是倒序处理的,每个数的前一个数的前缀和其实就是当前处理过的数中小于它的数的个数,也就是原数列中他后面的数字里小于它的个数,不必考虑会有多算或少算的情况发生。

    代码实现:

            for(i=n;i>0;--i){
    		add(a[i]);
    		ans+=sum(a[i]-1);
    	}
    

    方案二:正序做树状数组,那么当前下标减掉当前数字的前缀和即为以该数为较小数的逆序对个数。

    因为是正序,那么对于每个当前的数,已加入的数字个数(算当前数)即为当前数字在数列中的下标,也就是树状数组中已经加入了这么多个数,那么他的前缀和代表小于它且在他前面的数的个数,用总数减掉前缀和即为以该数为较小数的逆序对个数,同样,我们也不需要考虑多算或少算的情况发生。

    代码实现:

            for(i=1;i<=n;++i){
    		add(a[i]);
    		ans+=i-sum(a[i]);
            }
    

    3.离散化的两种方式

    因为在做树状数组时我们要以数字为下标,所以我们要对数列中的数进行离散化。

    方案一:copy出一个数组,sort一遍,以该数的地址为新数。

    代码实现:(b为copy出来的数列)

    void discretize(){
    	sort(b+1,b+1+n);
    	unique(b+1,b+1+n)-b-1;
    	for(i=1;i<=n;++i) a[i]=lower_bound(b+1,b+1+n,a[i])-b;
    }
    

    方案二:对于每个数开结构体,一个记录数字一个记录当前序号,按照数字sort一遍,序号数列即为离散后的新数列,其证明大家可以自己出几组数据比划比划就知道啦。

    代码实现:(cmp函数即为返回数字相比的结果)

    void discretize(){
         for(i=1;i<=n;++i){
    	 a[i].num=rd();
    	 a[i].d=i;
            sort(a+1,a+1+n,cmp);
        }
    

    二、相关题目

    1.[洛谷P1908]逆序对

    Description

    猫猫TOM和小老鼠JERRY最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。最近,TOM老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中ai>aj且i<j的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。

    输入格式:
    第一行,一个数n,表示序列中有n个数。
    第二行n个数,表示给定的序列。

    输出格式:给定序列中逆序对的数目。

    Solution

    裸的逆序对板子,直接套用即可。

    Code

    #include<iostream>
    #include<cmath>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    
    using namespace std;
    
    int a[40010],b[40010],c[40010],n,m,i,j,k;
    
    inline int rd(){
        int x=0;
        char c=getchar();
    	bool f=false;
    	while(!isdigit(c)){ 
    		if(c=='-') f=true;
    		c=getchar();
    	}
    	while(isdigit(c)){
    		x=(x<<1)+(x<<3)+(c^48);
    		c=getchar();
    	}
    	return f?-x:x;
    }
    
    void discretize(){
    	sort(b+1,b+1+n);
    	unique(b+1,b+1+n)-b-1;
    	for(i=1;i<=n;++i) a[i]=lower_bound(b+1,b+1+n,a[i])-b;
    }
    
    
    inline int lowbit(int x){return x&-x;}
    
    int add(int x,int k){
    	for(int i=x;i<=n;i+=lowbit(i))c[i]+=k;
    }
    
    int sum(int x){
    	int ret=0;
    	for(int i=x;i>0;i-=lowbit(i)) ret+=c[i];
    	return ret;
    }
    
    int main(){
    	n=rd();
    	for(i=1;i<=n;++i) a[i]=b[i]=rd();
    	discretize();
    	long long ans=0;
    	for(i=n;i>0;--i){
    		add(a[i],1);
    		ans+=sum(a[i]-1);
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    2.[NOI导刊2010提高&洛谷P1774]最接近神的人

    题目题解:http://www.cnblogs.com/COLIN-LIGHTNING/p/8621767.html

    3.[NOIP2013提高&洛谷P1966]火柴排队

    题目题解:http://www.cnblogs.com/COLIN-LIGHTNING/p/8621855.html

  • 相关阅读:
    【C/C++】例题5-4 反片语/算法竞赛入门经典/C++与STL入门/映射:map
    【VSCode】如何打开全屏模式/退出全屏模式
    【合同】电子科技大学/外协合同/采购合同
    新的开始
    ubuntu server 1604 搭建FTP服务器
    vim的查找功能
    Ubuntu改坏sudoers后无法使用sudo的解决办法
    ubuntu server 1604 配置网络信息
    ubuntu server 1604 关机和重启
    ubuntu server 1604 设置笔记本盒盖 不操作
  • 原文地址:https://www.cnblogs.com/COLIN-LIGHTNING/p/8621294.html
Copyright © 2011-2022 走看看