(HDU 5792)World is Exploding <树状数组+去重> 多校训练5

World is Exploding
Problem Description
Given a sequence A with length n,count how many quadruple (a,b,c,d) satisfies: a≠b≠c≠d,1≤a< b≤n,1≤c< d≤n,Aa< Ab,Ac>Ad.

Input
The input consists of multiple test cases.
Each test case begin with an integer n in a single line.

The next line contains n integers A1,A2⋯An.
1≤n≤50000
0≤Ai≤1e9

Output
For each test case,output a line contains an integer.

Sample Input
4
2 4 1 3
4
1 2 3 4

Sample Output
1
0

Author
ZSTU

Source
2016 Multi-University Training Contest 5

题意:
给一组序列,从中找出一个四元组,使四个元素下标两两不同,且a<b,c<d有Xa < Xb , Xc > Xd。问一共有多少组满足要求的四元组。

分析:
对于要求出的结果,我们可以用树状数组很快的求出对于a[i],左边比他小的数的数目ls[i],左边比他大的数的数目lb[i],右边比他小的数的数目rs[i],右边比他大的数的数目rb[i]。
我们可以利用所有的ls[i],lb[i]或者rs[i],rb[i]求出逆序对的个数suml和顺序对的个数sumb。
而 suml * sumb的结果是所有的逆序对和所有的顺序对匹配的结果,而题目要求a,b,c,d互不相等,这里面存在重复的情况:a,c重复,b,d重复,c,b重复,a,d重复(不存在3者或4者重复的情况)

所以我们将结果减去重复的即可:
rs[i]*rb[i]为a,c重合的情况 , ls[i]*lb[i]为b,d重合的情况 , ls[i]*rs[i]为c,b重合的情况 , lb[i]*rb[i]为a,d重合的情况

注意:
在代码中我们用四个数组分别维护了四个值得大小
由于在题目中可能出现两个数相等的情况,而且我们在使用树状数组时要知道每个数(没有重复的情况)的从小到大位置(便于取值和更新)。
所以我们用tmp[]数组存所有的数,并将它排序后去重,然后我们利用lower_bound()函数即可快速求出每个数的位置。

关于更新的处理:例如如下代码

for(int i=0;i<n;i++)//获取ls,lb
        {
            a[i] = lower_bound(tmp,tmp+cnt,a[i])-tmp+1;//获取a[i]在数组中从小到大的位置,便于更新
            ls[i] = getl(a[i]-1,tls);
            lb[i] = getr(a[i]+1,tlb);
            suml += ls[i]; sumb += lb[i];
            upr(a[i],tls);//更新
            upl(a[i],tlb);
        }

在计算ls[],lb[]时我们是从左向右插入的,ls[i] = getl(a[i]-1,tls);:获取在a[i]左边(已经插入)的数的数目,对于lb[i] = getr(a[i]+1,tlb);同理
upr(a[i],tls); : 由于a[i]已经插入,所以后面插入的且位置在a[i]后面的值都要更新
upl(a[i],tlb);同理

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;

const int maxn = 50010;
int a[maxn],tmp[maxn];
int n,cnt;//cnt:去重后的个数
int ls[maxn],lb[maxn],rs[maxn],rb[maxn];//s:左边小于当前位的数的个数 , rs:右边小于当前位的数的个数,lb:左边大于当前位的数的个数,rb:右边大于当前位的数的个数
int tls[maxn],tlb[maxn],trs[maxn],trb[maxn];//相应的树状数组
LL ans,suml,sumb;//答案,逆序对个数,顺序对个数

void init()
{
    memset(tls,0,sizeof(tls));
    memset(tlb,0,sizeof(tlb));
    memset(trs,0,sizeof(trs));
    memset(trb,0,sizeof(trb));
    ans = suml = sumb = 0;
}

LL getl(int x,int *tr)//获取左边的值
{
    LL res = 0;
    while(x > 0)
    {
        res += (LL)tr[x];
        x -= (x&-x);
    }
    return res;
}

LL getr(int x,int *tr)//获取右边的值
{
    LL res = 0;
    while(x <= maxn)
    {
        res += (LL)tr[x];
        x += (x&-x);
    }
    return res;
}

void upl(int x,int *tr)//向左边更新
{
    while(x > 0)
    {
        tr[x] += 1;
        x -= (x&-x);
    }
}

void upr(int x,int *tr)//向右边更新
{
    while(x <= maxn)
    {
        tr[x] += 1;
        x += (x&-x);
    }
}


int main()
{
    while(scanf("%d",&n)==1)
    {
        init();
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            tmp[i] = a[i];
        }
        //去重
        sort(tmp,tmp+n);
        cnt = unique(tmp,tmp+n)-tmp;

        for(int i=0;i<n;i++)//获取ls,lb
        {
            a[i] = lower_bound(tmp,tmp+cnt,a[i])-tmp+1;//获取a[i]在数组中从小到大的位置,便于更新
            ls[i] = getl(a[i]-1,tls);
            lb[i] = getr(a[i]+1,tlb);
            suml += ls[i]; sumb += lb[i];
            upr(a[i],tls);//更新
            upl(a[i],tlb);
        }
        for(int i=n-1;i>=0;i--)//获取rs,rb
        {
            rb[i] = getr(a[i]+1,trb);
            rs[i] = getl(a[i]-1,trs);
            upr(a[i],trs);
            upl(a[i],trb);
        }

        ans = suml * sumb;
        for(int i=0;i<n;i++)
        {
            ans -= (rs[i]*rb[i] + ls[i]*lb[i] + ls[i]*rs[i] + lb[i]*rb[i]);
            //rs[i]*rb[i]为a,c重合的情况 , ls[i]*lb[i]为b,d重合的情况 , ls[i]*rs[i]为c,b重合的情况 , lb[i]*rb[i]为a,d重合的情况
        }
        printf("%lld\n",ans);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值