http://poj.org/problem?id=3928
题意:有n个乒乓球爱好者,每个人都有不同的权能值a[i],每场比赛需要三个人,两名选手,一名裁判。有规定,裁判必须住在两名选手中间,并且技能值也要在两名选手中间。问一共能组织多少场比赛。
思路:只考虑第i(2 <= i <= n-1)个人当裁判的情况。维护两个数组c[]和d[]。c[i]表示a[1]~a[i-1]有c[i]个数比a[i]小,那么就有(i-1-c[i])个比a[i]大。同样的,d[i]表示a[i+1]~a[n]有d[i]个数比a[i]小,那么就有(n-i-d[i])个比a[i]大。根据乘法原理和加法原理,i当裁判时,有 c[i]*(n-i-d[i]) + d[i]*(i-c[i]-1)种比赛。这样问题就转化为求c[i]和d[i]。
求c[i]:从左到右扫描所有的a[i],令x[j]表示到目前所有的a[i]中是否存在一个a[i] == j( x[j] = 0 表示不存在,x[j] = 1表示存在).
则c[i]就是前缀和x[1]+x[2]+....+x[ a[i] -1 ]。用树状数组求前缀和即区间统计,当扫描到a[i]时,先更新a[i](往右走,边走边“往上爬”),然后求前缀和(往左走,边走边“往上爬”)。
初始时所有的x[i] = 0。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define LL long long
using namespace std;
const int maxn = 100010;
const int maxm = 20010;
int test,n,a[maxm],c[maxm],d[maxm],x[maxn];
int lowbit(int x)
{
return x & (-x);
}
int sum(int k)
{
int ans = 0;
while(k)
{
ans += x[k];
k -= lowbit(k);
}
return ans;
}
void update(int k)
{
while(k <= 100000)
{
x[k] += 1;
k += lowbit(k);
}
}
int main()
{
while(~scanf("%d",&test))
{
while(test--)
{
scanf("%d",&n);
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
memset(x,0,sizeof(x));
for(int i = 1; i <= n; i++)
{
c[i] = sum(a[i]-1); //求c[i]
update(a[i]);
}
memset(x,0,sizeof(x));
for(int i = n; i >= 1; i--)
{
d[i] = sum(a[i]-1);//求d[i]
update(a[i]);
}
LL ans = 0;
for(int i = 2; i < n; i++)
ans += (LL)c[i]*(n-i-d[i]) + (LL)d[i]*(i-c[i]-1);
printf("%lld\n",ans);
}
}
return 0;
}