离散化加树状数组求逆序数。

【转自BLOG】点击打开链接

【1 POJ.2299】

离散化是一种常用的技巧,有时数据范围太大,可以用来放缩到我们能处理的范围 因为其中需排序的数的范围0---

999999999;显然数组不肯能这么大;而N的最大范围是500000;故给出的数一定可以与1.。。。N建立一个一一映射;

 

这里用一个结构体

struct Node

 {

      int v,order;

 }a[510000];

和一个数组aa[510000];

 

其中v就是原输入的值,order是下标;然后对结构体按v从小到大排序;此时,v和结构体的下标就是一个一一对应关系,而且

满足原来的大小关系;

for(i=1;i<=N;i++)

     aa[a[i].order]=i;

 

然后a数组就存储了原来所有的大小信息;比如 9 1 0 5 4 ------- 离散后aa数组就是 5 2 1 4 3;具体的过程可以自己

用笔写写就好了。

 

离散之后,怎么使用离散后的结果数组来进行树状数组操作,计算出逆序数? 

如果数据不是很大,可以一个个插入到树状数组中,每插入一个数,统计比他小的数的个数,对应的逆序为 i- getsum(aa

[i]),其中i为当前已经插入的数的个数,getsum(aa[i])为比aa[i]小的数的个数,i- sum(aa[i]) 即比aa[i]大的个数,

即逆序的个数但如果数据比较大,就必须采用离散化方法。

【AC code】

//POJ.2299
//Ultra-QuickSort

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=500010;
#define ll long long
struct node{
    int v,order;
    friend bool operator<(const node &p,const node &q)
    {
        return p.v<q.v;
    }
}a[maxn];//输入部分
int aa[maxn];//离散化后的数组
int c[maxn];//树状数组
int n;
void init()
{
    memset(c,0,sizeof(c));
}
int getsum(int i)
{
    int s=0;
    while(i>0)
    {
        s+=c[i];
        i-=i&-i;
    }
    return s;
}
void add(int i,int x)
{
    while(i<=n)
    {
        c[i]+=x;
        i+=i&-i;
    }
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0) break;
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i].v);
            a[i].order=i;
        }
        sort(a+1,a+n+1);
        for(int i=1; i<=n; i++)
        {
            aa[a[i].order]=i;
        }
        init();
        ll ans=0;
        for(int i=1; i<=n; i++)
        {
            add(aa[i],1);
            ans+=i-getsum(aa[i]);
        }
        printf("%lld\n",ans);
    }
}

【2 HDU.2492】

【题意】有两个人可以参加比赛,但是他们必须选一个裁判,并且这个裁判的能力值不能比这个人高,也不能比这个人低,求满足的方案数!对于每个人,只需要统计左边比他高,右边比他低的人数或者,统计左边比他低,右边比他高的人数,这两种选择的方案数之和就是整个的方案数。

【AC code】

//HDU.2492

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cctype>
#include<iostream>
using namespace std;
#define ll long long
const int maxn=100005;
int a[maxn],b[maxn],c[maxn],d[maxn],e[maxn],f[maxn];
int getInt(int &x)
{
    return scanf("%d",&x);
}
int n;
int low_bit(int x)
{
    return x&(-x);
}
void update(int x,int v)
{
   while(x<=maxn)
   {
       a[x]+=v;
       x+=low_bit(x);
   }
}
int get_sum(int x)
{
    int sum=0;
    while(x>0)
    {
        sum+=a[x];
        x-=low_bit(x);
    }
    return sum;
}

int main()
{
    int tt;ll ans;
    getInt(tt);
    while(tt--)
    {
        getInt(n);
        memset(a,0,sizeof(a));
        ans=0;
        for(int i=1;i<=n;i++)
        {
            getInt(b[i]);
        }
        for(int i=1;i<=n;i++)
        {
            update(b[i],1);
            c[i]=get_sum(maxn)-get_sum(b[i]);  //左边有多少个数比自己大
            d[i]=get_sum(b[i]-1);              //左边有多少个数比自己小
        }
        memset(a,0,sizeof(a));
        for(int i=n;i>=1;i--)
        {
            update(b[i],1);
            e[i]=get_sum(maxn)-get_sum(b[i]);   //右边有多少个数比自己大
            f[i]=get_sum(b[i]-1);               //右边有多少个数比自己小
        }
        for(int i=1;i<=n;i++)
        {
            ans+=c[i]*f[i]+d[i]*e[i];
        }
        printf("%lld\n",ans);
    }
    return 0;
}



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值