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

来源:互联网 发布:设计网络营销策划方案 编辑:程序博客网 时间:2024/05/16 11:54

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