问题分析:
当第i个人当裁判的时候,假设a[1]到a[i-1]中有c[i]个比a[i]小,就有(i-1)-c[i]个比a[i]大;同样的,假设a[i+1]到a[n]中有d[i]个比a[i]小,就有(n-i)-d[i]比a[i]大。
所以,当第i个人当裁判时,有c[i]*(n-i-d[i])+(i-1-c[i])*d[i]中比赛。
问题转换为求c[i], d[i]。
计算c[i],从左到右扫描所有的a[i],令x[j]=1/0表示当前扫描过的a[i]中是否有a[i]=j,c[i]=x[1]+x[2]+...+x[a[i-1]],如此这般求解前缀和。可以在O(nlogr)时间内计算所有的c[i]。计算d[i]同理。
我们动态的修改数组中的一个值,求解其连续和,正好可以使用树状数组。:)
/*
* 4329.c
*
* Created on: 25 April, 2015
* Author: gaoyida
* Problem: Ping Pong, Beijing 2008, LA 4329
* Method: BIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 20000+5
#define Maxn 100000+5
#define LL long long
LL c[N], d[N], x[Maxn];
int a[N], lowbit[Maxn];
void init() {
int i;
lowbit[0] = 0;
for (i = 1; i <= 100000; ++i)
lowbit[i] = i & -i;//lowbit[x]即x的二进制表达式中最右边的1所对应的值
}
void addForBit(LL cnt[], int i, int r) {
while(i <= r) {
cnt[i] += 1;
i += lowbit[i];
}
}
void sum(LL cnt[], LL f[], int i, int r) {
int pos = a[i];
LL ret = 0;
addForBit(cnt, pos, r);
pos = pos - 1;
while (pos > 0) {
ret += cnt[pos];
pos -= lowbit[pos];
}
f[i] = ret;
}
int main() {
int T;
scanf("%d", &T);
init();
while (T--) {
int n, i, r = 0;
scanf("%d", &n);
for (i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
if (a[i] > r) {
r = a[i];
}
}
memset(x, 0, sizeof(x));
memset(c, 0, sizeof(c));
for (i = 1; i <= n; ++i) {
sum(x, c, i, r);
}
memset(x, 0, sizeof(x));
memset(d, 0, sizeof(d));
for (i = 1; i <= n; ++i) {
sum(x, d, n-i+1, r);
}
LL res = 0;
for (i = 2; i < n; ++i) {
res += c[i] * (n-i-d[i]) + (i-1-c[i])*d[i];
}
printf("%lld\n", res);
}
return 0;
}
树状数组(二叉索引树):
在二叉索引树中,每层结点的lowbit相同,lowbit越大,越靠近根。对于结点i,如果它是左子结点,父节点编号是i+lowbit(i);如果是右子节点,父节点编号是i-lowbit(i)
构造出辅助数组C
Ci = Ai-lowbit(i)+1 + Ai-lowbit(i)+2 + ... + Ai
涉及两个操作:
int sum(int x) {
int res = 0;
while (x > 0) {
res += C[x];
x -= lowbit(x);
}
return res;
}
void add(int x, int v) {
while (x <= n) {
C[x] += v;
x += lowbit(x);
}
}
这两个操作的时间复杂度都为O(logn)
树状数组对点修改求连续和代码简洁,如果出现了段修改求连续和可能线段树更好。
【1】 内容参考:《算法竞赛入门经典训练指南》