/*
Ping pong
Time Limit: 1000MS
Memory Limit: 65536K
Description
N(3<=N<=20000) ping pong players live along a west-east street(consider the street as a line segment). Each player has a unique skill rank. To improve their skill rank, they often compete with each other. If two players want to compete, they must choose a referee among other ping pong players and hold the game in the referee's house. For some reason, the contestants can't choose a referee whose skill rank is higher or lower than both of theirs. The contestants have to walk to the referee's house, and because they are lazy, they want to make their total walking distance no more than the distance between their houses. Of course all players live in different houses and the position of their houses are all different. If the referee or any of the two contestants is different, we call two games different. Now is the problem: how many different games can be held in this ping pong street?
Input
The first line of the input contains an integer T(1<=T<=20), indicating the number of test cases, followed by T lines each of which describes a test case. Every test case consists of N + 1 integers. The first integer is N, the number of players. Then N distinct integers a1, a2 ... aN follow, indicating the skill rank of each player, in the order of west to east. (1 <= ai <= 100000, i = 1 ... N).
Output
For each test case, output a single line contains an integer, the total number of different games.
Sample Input
1
3 1 2 3
Sample Output
1
n个选手站成一排,每个选手一个数字,按位置分别为a1,a2,...,an,现在要找出三个人,处于中间位置的选手,他的数字也是中间大小,问这样三个人可以找几组
首先基本思想是找到中间那个人,设位置是p,数字为a[p],然后向两边遍历,如果左边比a[p]小的有c[p]个,右边比a[p]小的有d[p]个,那以p位置为中点的选法就有c[p]*(n-p-d[p])+(p-c[p])*d[p],记为s[p],从1到n扫一遍,记录每一个c[i]和d[i],最后相加就可以啦。
数据范围:选手数n,[3,20000],数字范围[1,1e5]
空间复杂度:3个选手数大小的数组,3×2e4
时间复杂度:1、1-n扫一遍,复杂度n;2、同时对每个数的操作,左右各扫一遍,并计算出s[p],复杂度n;前两步复杂度n^2;3、s[n]相加,复杂度n;
总体复杂度为n^2+n,max=4e8+2e4;
有咩优化的咩?
比a[p]小的数个数是一定的,用b[p]记录,那d[p]=b[p]-c[p],数组b棵利用以a[i]为index的树状数组,a[i]的个数为该数组的值,记为x[a[i]],表示有无扫到这个数,若有为1,没有为0,求出index<a[p]的x[i]的和,就是所需的b[p]。
这样不难想到,在遍历中间者的时候,利用树状数组,在遍历到p位置上的选手时,所求的c[p]就是此时x[1]到x[a[p]]的和,注意,不能把a[p]加进去
这样,时间上,1-n遍历中间者是n的复杂度,更新节点和求和是logn的复杂度
空间上,所需数组:a[],c[],h[]。
原数组a[],大小为n的最大值=2e4,
h[]是树状数组的辅助数组,因为本题只需要区间上的和,辅助数组h可代替数组x。树状数组x[]的范围为数字a[i]的最大值=1e5+1,
辅助数组c[],因为遍历时x[]会有变动,所以需要把p位置前扫过的比a[p]小的个数记录下来,所以需要辅助数组c[],范围同a[],=2e4;
至于上述提到的b[]只用一次,在需要的时候调用树状数组x(1,a[p])的求和即可,s[p]=c[p]*(n-p-b[p]+c[p])+(p-c[p])*(b[p]-c[p]),而s[p]的值只需在求得的时候加到结果sum就可以啦,不需要另外开空间。所以总共需要空间2e4+2e4+1e5+1。
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int nsize = 2e4;
const int asize = 1e5+1;
int n,a[nsize],c[nsize],x[asize],h[asize],maxa;
void add(int idx ,int val){
while (idx <= maxa){//树状数组最大值是数组a里的最大值,不是n
h[idx] += val;
idx += (idx & -idx);
}
return;
}
int getsum(int idx){
int sum=0;
while(idx > 0){
sum +=h[idx];
idx -= (idx &-idx);
}
return sum;
}
int main(){
int t;
long long answer,tmpl,tmpr,b;//用int wa了
scanf("%d",&t);
while(t--){
memset(h,0,sizeof(h));//重置树状数组为0
answer = 0;
maxa = -1;
scanf("%d",&n);
for(int i = 1;i <=n; i++){
scanf("%d",&a[i]);
maxa = max(maxa, a[i]);
}
for(int i = 1; i <= n; i++){
add(a[i],1);
if(i >= 2 && i < n){
c[i] = getsum(a[i]) - 1;
}
}
for(int i = 2; i < n; i++){
b = getsum(a[i]) - 1;
tmpl = c[i] * (n - i - b + c[i]);
tmpr = (i - 1 - c[i]) * (b - c[i]);
answer += (tmpl +tmpr);
}
printf("%lld\n",answer);
}
}
poj 3928 树状数组
最新推荐文章于 2024-11-05 17:16:24 发布