树状数组求逆序对

题目:http://acm.hdu.edu.cn/showproblem.php?pid=5792

分析:

计算像a,b这样上升的有la对,像c,d这样下降的有lb对,ans=la*lb。这样是有重复的,重复的就是a与c重合,a与d重合,b与c重合,b与d重合这四种情况。那么减去这四种情况就ok了。

可以用树状数组预处理出每一位i的左边比a[i]大的有多少La[],少的有多少Li[],右边比a[i]大的有多少Ra[],少的有多少Li[]。最后ans减去Ri[i]*Ra[i];    Li[i]*La[i];   Li[i]*Ri[i];   La[i]*Ra[i]  就行了。

Ac code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=5e4+5;
int b[maxn];
ll Ll[maxn],Lr[maxn],Rl[maxn],Rr[maxn];
int k;
struct Node{
   int val;
   int id;
   bool operator<(const Node& a)const
   {
       return val<a.val;
   }
};
Node a[maxn];
int lowbit(int x)
{
    return x&(-x);
}
void add(int pos,int val,ll c[])
{
    while(pos<maxn)//注意是<maxn,而不是n
    {
        c[pos]+=val;
        pos+=lowbit(pos);
    }
}
ll query(int pos,ll c[])
{
    ll sum=0;
    while(pos>0)
    {
        sum+=c[pos];
        pos-=lowbit(pos);
    }
    return sum;
}
ll c1[maxn],c2[maxn];
int cnt[maxn];///用于计算重复的数的个数
int main()
{
    int n;
    while(~scanf("%d",&n)){
        memset(c1,0,sizeof c1);
        memset(c2,0,sizeof c2);
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++) scanf("%d",&a[i].val),a[i].id=i;
        sort(a+1,a+1+n);
        k=1;
        for(int i=1;i<=n;i++)
        {
            if(i!=1&&a[i].val!=a[i-1].val) ++k;
            b[a[i].id]=k;///离散化
        }
        ll sum1=0,sum2=0;
        for(int i=1;i<=n;i++)
        {
            add(b[i],1,c1);
            ll num=query(b[i],c1);
            cnt[b[i]]++;
            Lr[i]=i-num;///左边比b[i]大的数的个数=插入的数的总个数-小于等于b[i]的数的个数
            Ll[i]=num-cnt[b[i]];///左边比b[i]小的数的个数,注意要减去cnt[b[i]]而不是1,因为前面也可能已经出现了b[i]
            sum1+=Lr[i];///逆序对数
        }
        memset(cnt,0,sizeof cnt);
        for(int i=n;i>=1;i--)
        {
            add(b[i],1,c2);
            ll num=query(b[i],c2);
            cnt[b[i]]++;
            Rr[i]=(n-i+1)-num;
            Rl[i]=num-cnt[b[i]];
            sum2+=Rr[i];///正序对数
        }
        ll ans=sum1*sum2;
        for(int i=1;i<=n;i++)
        {
            ans=ans-Rl[i]*Rr[i];
            ans=ans-Ll[i]*Lr[i];
            ans=ans-Ll[i]*Rl[i];
            ans=ans-Lr[i]*Rr[i];
        }
        printf("%lld\n",ans);
    }
    return 0;
}

最后贴个树状数组求逆序对的板子:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
 
int n,tree[100010];
void add(int k,int num)
{
    while(k<=n)
    {
        tree[k]+=num;
        k+=k&-k;
    }
}
 
int read(int k)
{
    int sum=0;
    while(k)
    {
        sum+=tree[k];
        k-=k&-k;
    }
    return sum;
}
struct node
{
    int val,pos;
}a[100010];
bool cmp(node a,node b)
{
    return a.val < b.val;
}
int main(void)
{
    int i,j;
    int b[100010];
    while(scanf("%d",&n)==1)
    {
        memset(tree,0,sizeof(tree));
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i].val);
            a[i].pos = i;
        }
        sort(a+1,a+1+n,cmp);
        int cnt = 1;
        for(i=1;i<=n;i++)
        {
            if(i != 1 && a[i].val != a[i-1].val)
                cnt++;
            b[a[i].pos] = cnt;
        }
        LL sum = 0;
        for(i=1;i<=n;i++)
        {
            add(b[i],1);
            sum += (i - read(b[i]));
        }
        printf("%lld\n",sum);
    }
 
    return 0;
}

 

外加归并排序求逆序对的板子:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
ll ans;
void Merge(int a[],int l,int mid,int r)
{
    int b[100005];
    for(int i=l; i<=r; i++)
        b[i]=a[i];
    int i,j,k;
    for(i=l,j=mid+1,k=l; i<=mid&&j<=r; ++k)
        if(b[i]<=b[j])
        {
            a[k]=b[i];
            ++i;
        }
        else
        {
            a[k]=b[j];
            ++j;
            ans+=mid-i+1;///若有序表b中,b[i]>b[j],那么[i,mid]这个区间都比b[j]大,逆序对数mid-i+1
        }
    while(i<=mid) a[k++]=b[i++];
    while(j<=r) a[k++]=b[j++];
}
void divide(int a[],int l,int r)
{
    if(l>=r)
        return;
    int mid=(l+r)>>1;
    divide(a,l,mid);
    divide(a,mid+1,r);
    Merge(a,l,mid,r);
}
int main()
{
    int a[maxn];
    int x,n;
    while(~scanf("%d",&n))
    {
        ans=0;
        for(int i=1; i<=n; i++)
            scanf("%d",&a[i]);
        divide(a,1,n);
        //for(int i=1; i<=n; i++) printf("%d%c",a[i],i==n?'\n':' ');
        printf("%lld\n",ans);
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值