题目链接:https://vjudge.net/contest/238229#problem/D
白书上树状数组的例题,刚好学完,正好熟悉一下树状数组的常规操作。
对于每个i当裁判时的情况,设从a1到ai-1有ci个小于ai的数,则就有i-1-ci个比ai大的数,从ai+1到an有di个小于ci的数,就有n-i-di个比ai大的数,这样,大数与小数配对,小数与大数配对,就有ci*(n-i-di)+di*(i-1-ci)种不同的组法,问题就变成求ci和di。
如何求ci和di呢?从0到ai的最大值1e5遍历一遍,如果这个值存在(判断在a[i]中是否出现),x[a[i]]=1,否则等于0,这样,小于a[i]的值的个数就变为x[1]+x[2]+......+x[a[i]-1](仔细想想),那么问题就变成如何求x[i]得前缀和了,就可以用树状数组求了。具体实现就是,输入一个a[i],将它用add操作加入到树状数组中,用sum求前缀和。还有很多细节,看代码吧。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn=2e4+10;
const int maxm=1e5+10;
int n;
int c[maxn];
int d[maxn];//标号小于和大于i的,技能值比ai小的个数
int a[maxn];
int s[maxm];//树状数组
int lowbit(int x)
{
return x&-x;
}
int sum(int x)
{
int ret=0;
while(x>0)
{
ret+=s[x];
x-=lowbit(x);//向左递推,求和
}
return ret;
}
void add(int x,int d)
{
while(x<maxm)//这里注意一下,你求的是值的前缀和
{
s[x]+=d;
x+=lowbit(x);//向右递推,修改元素
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(s,0,sizeof(s));
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
//利用树状数组求c[i]
for(int i=1;i<=n;i++)
{
add(a[i],1);
c[i]=sum(a[i])-1;//直接求c[i]的前缀和,自己好好想象一下树状数组是如何保存前缀和的
}
memset(s,0,sizeof(s));//记得初始化
for(int i=n;i>=1;i--)
{
add(a[i],1);
d[i]=sum(a[i])-1;//后缀和,满足d[i]数组的定义
}
ll ans=0;
for(int i=2;i<n;i++)
{
ans+=c[i]*(n-i-d[i])+d[i]*(i-c[i]-1);
}
printf("%lld\n",ans);
}
return 0;
}