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;
}