权值线段树2(求逆序对)

题目链接:逆序对1逆序对2

都是板子,一摸一样,双倍积分

一.逆序对是什么?

对于给定的一段正整数序列,逆序对就是序列中ai>aj且i< j的有序对

通俗来讲,就是有一列数,如果有这样两个数m,n满足

  • m在n前面
  • m比n大

那么m,n就是一对逆序对,现在我们要求的就是这种逆序对个数。

二.权值线段树实现方法

我们能够想到这样一种方法:

  1. 读入一个数,查找之前读入的数中比它大的数的个数,累加答案
  2. 把它丢进某种数据结构中,方便之后查找

而这“某种数据结构”不就是权值线段树吗?

查询:
对于查询数x与此时左子树右边界mid( mid=(l+r)>>1 )
若x<=mid,则答案=左子数比x大的数的个数+右子树的数的个数
若x>mid,则答案=右子树比x大的数的个数
递归处理即可
一定记住权值线段树下标维护的是值域!!

插入:
与先前相同

但是

a[i]<=INT_MAX
权值线段树会爆!!!

怎么办?
n好像十分友善:n\(<=\)\(5*10^{5}\)
离散化
离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。
我们发现并不需要记录每一个数的具体数值,只要记录他们之间的大小关系即可。
具体看代码。

code:

#include<bits/stdc++.h>
using namespace std;
long long a[500005],ans,n;
struct MM{
    long long ii,num;
}b[500005];
struct MMM{
    long long l,r,sum;
}tree[2000005];
bool cmp(MM x,MM y)
{
    return x.num<y.num;
}
void build(long long root,long long L,long long R)
{
    tree[root].l=L;
    tree[root].r=R;
    if(tree[root].l==tree[root].r)
        return;
    long long mid=(L+R)>>1;
    build(root<<1,L,mid);
    build(root<<1|1,mid+1,R);
    return;
}
void updata(long long root,long long v)
{
    if(tree[root].l==tree[root].r)
    {
        tree[root].sum++;
        return;
    }
    long long mid=(tree[root].l+tree[root].r)>>1;
    if(v<=mid) updata(root<<1,v);
    else updata(root<<1|1,v);
    tree[root].sum=tree[root<<1].sum+tree[root<<1|1].sum;
    return;
}
long long query(long long root,long long x)
{
    if(tree[root].l==tree[root].r) return tree[root].sum;
    long long mid=(tree[root].l+tree[root].r)>>1;
    if(x<=mid) return query(root<<1,x)+tree[root<<1|1].sum;
    return query(root<<1|1,x);
}
int main()
{
    cin>>n;
    for(long long i=1;i<=n;i++)//这里离散化开始
    {
        cin>>a[i];
        b[i].num=a[i];
        b[i].ii=i;
    }
    sort(b+1,b+n+1,cmp);
    for(long long i=1,j=0;i<=n;i++)
    {
        if(b[i].num!=b[i-1].num||i==1) j++;
        a[b[i].ii]=j;
    }//这里离散化结束,可以看看或者自己算算加深理解
    build(1,1,n);
    for(long long i=1;i<=n;i++)
    {
        ans+=query(1,a[i]+1);//这里有个小细节:为什么要加一呢?因为权值线段树算的是大于等于x的数的个数,而我们要求的是严格大于x的数的个数,所以加一(具体可以看看查询函数或手算试试)
        updata(1,a[i]);
    }
    cout<<ans;
    return 0;
}

转载于:https://www.cnblogs.com/zmyzmy/p/9595230.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值