题意:
一条街道上有N个选手,他们要打乒乓球赛,每个人有一个自己的skill rank,且是N个不同的数。每场比赛需要一个裁判,要求这个裁判的skill rank不能高于也不能低于这两个选手,且在街上住的位置必须在两个选手之间。问可以组织多少场比赛。
开始想到了可以用树状数组处理每个人前面有多少sr(skill rank)大于他的。但是开始想法是枚举起止点开中间有多少符合要求的裁判,是N方的。看了刘汝佳白书才知道正确做法是枚举中间的裁判。
对于裁判i,假设我们知道他之前sr小于他的人数Front【i】,和之后sr大于他的人数Back【i】。
那么他能组织的比赛数量就是Front【i】*Back【i】+(i-1-Front【i】)*(N-i-Back【i】) (左小右大和左大右小)
这样O(n)遍历一遍就行了。
求Front【i】,Back【i】的方法和求逆序数一样。
求出Front【i】后求Back【i】不需要重新排序,清空c数组后从后往前再做一遍,但因为遍历是从后到前,而查询还是从前往后的。所以做的时候把需要+1的位置ord修改为N-i-ord就可以了。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
struct node{
int num,ord;
}G[20010];
int a[20010];
int c[20010];
int Front[20010];
int Back[20010];
int N;
int lowbit(int n){
return n&(-n);
}
int sum(int p){
int res=0;
while(p<=N){
res+=c[p];
p+=lowbit(p);
}
return res;
}
void add(int p,int n){
while(p>0){
c[p]+=n;
p-=lowbit(p);
}
}
bool cmp(node a,node b){
if(a.num==b.num) return a.ord<b.ord;
return a.num<b.num;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d",&N);
memset(c,0,sizeof(c));
for(int i=1;i<=N;i++){
scanf("%d",&G[i].num);
G[i].ord=i;
}
sort(G+1,G+N+1,cmp);
for(int i=1;i<=N;i++){
Front[i]=sum(G[i].ord);
add(G[i].ord,1);
}
memset(c,0,sizeof(c));
for(int i=N;i>=1;i--){
int pos=N-G[i].ord+1;
Back[i]=sum(pos);
add(pos,1);
}
long long res=0;
for(int i=1;i<=N;i++){
res+=Front[i]*Back[i];
res+=(N-i-Back[i])*(i-1-Front[i]);
}
printf("%lld\n",res);
}
return 0;
}