一共有(n)个人,在一般化的情况下,第(i)个人作为裁判时,设在(i)的左边存在(c_i)个人的技能值小于(i),在(i)的右边存在(d_i)个人的技能值大于(i)。
则在(i)右边大于(i)的有(n-i-d_i)个人,在(i)左边大于(i)的有(i-1-c_i)个人。
根据乘法原理,一共有(c_i*(n-i-d_i)+(i-1-c_i)*d_i)种可能。
遍历(2) 到(n-1)号,根据上式可以求出所有可能性。
利用树状数组保存(c)和(d)两个数组,具体来说有如下例子
3 4 2 1 5 6 8
以计算(c)数组为例,初始情况下,(A)和(C)(注意,这里是大C,目的是为了与刘汝佳的书一样,方便阅读)
| C | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|--- |
| A | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
考察第一个(3),在C数组里初始什么都没有,(sum(3))的值为0,但要在A数组中添加一个(3)的对应数量为1。此时(c_1=0)。
| C | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
|--- |
| A | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
考察第二个(4),首先在(A)数组中利用(add(4))给对应位置加上1个数量,然后计算(sum(4)),将值赋给(c_2),其值为1。
| C | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
|--- |
| A | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
以下以此类推,不过最后会发现,大(C)数组和小(c)数组中的值不同,因为小(c)是在动态添加的过程中,以后如果有比当前考察对象更小的数值加入进来的话,该考察值不会改变,而大(C)是会改变的。
#include <string.h>
#include <iostream>
using namespace std;
const int maxn = 100000+10;
int i, n, bit[maxn];
inline void add(int x,int d){while(x<=maxn){bit[x]+=d;x+=x&-x;}}
inline int sum(int x){int ret=0;while(x>0){ret+=bit[x];x-=x&-x;}return ret;}
inline int query(int x,int y){return sum(y)-sum(x-1);}
inline void read(int&a){char c;while(!(((c=getchar())>='0')&&(c<='9')));a=c-'0';while(((c=getchar())>='0')&&(c<='9'))(a*=10)+=c-'0';}
const int maxN = 20000+10;
int main() {
int T; cin >> T;
while (T--) {
int c[maxn], d[maxn], a[maxN];
memset(c, 0, sizeof(c));
memset(d, 0, sizeof(d));
cin >> n;
for (i = 0; i < n; ++i) read(a[i]);
memset(bit, 0, sizeof(bit));
for (i = 0; i < n; ++i) { add(a[i], 1); c[i] = sum(a[i]-1); }
memset(bit, 0, sizeof(bit));
for (i = n-1; i >= 0; --i) { add(a[i], 1); d[i] = sum(a[i]-1); }
long long cnt = 0;
for (i = 1; i < n-1; ++i) cnt += c[i]*(n-i-d[i]-1) + d[i]*(i-c[i]);
cout << cnt << endl;
}
return 0;
}