1. 程式人生 > >POJ 3928 Ping pong 線段樹

POJ 3928 Ping pong 線段樹

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個乒乓球愛好者,每個人都有一個不同的技能值 a 。每場比賽需要選3個人:1個裁判,2個選手,有一個奇怪的規定:裁判必須住在選手中間,且裁判的技能值也要在選手中間。求能有多少種比賽。

思路:
可以將裁判固定, 然後求兩邊分別有多少人比他大和比他小。

這是就可以通過線段樹求解, 建兩顆線段樹分別求左邊的比裁判小的和右邊比裁判小的, 然後比裁判大的就可以通過那部分可以通過左右兩邊的總人數分別減去就可以求得。 

線段樹維護的是區間能力值總和。

求單點的不同的場次的式子如下:

ans1[i]*(n-i-ans2[i])+ans2[i]*(i-1-ans1[i])

程式碼如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int maxn=100005;
int t;
int n;
int a[maxn];
//兩顆線段樹
int tree1[maxn<<2];
int tree2[maxn<<2];
//求某點右邊能力值大的人數
int ans1[maxn];
//求某點左邊能力值大的人數
int ans2[maxn];
void pushup (int re,int * t)
{
    t[re]=t[re<<1]+t[re<<1|1];
}
void build (int l,int r,int re,int *t)
{
    t[re]=0;
    if(l==r)
        return;
    int mid=(l+r)>>1;
    build (l,mid,re<<1,t);
    build (mid+1,r,re<<1|1,t);
}
void update (int l,int r,int re,int *t,int loc)
{
    if(l==r)
    {
        t[re]++;
        return;
    }
    int mid=(l+r)>>1;
    if(loc<=mid)
        update (l,mid,re<<1,t,loc);
    else
        update (mid+1,r,re<<1|1,t,loc);
    pushup (re,t);
}
int query (int left,int right,int re,int l,int r,int *t)
{
    if(l>=left&&r<=right)
    {
        return t[re];
    }
    int ans=0;
    int mid=(l+r)>>1;
    if(mid>=left)
        ans+=query (left,right,re<<1,l,mid,t);
    if(mid<right)
        ans+=query (left,right,re<<1|1,mid+1,r,t);
    return ans;
}
int main()
{
    scanf("%d",&t);
    while (t--)
    {

      scanf("%d",&n);
      build (1,maxn,1,tree1);
      build (1,maxn,1,tree2);
      //正向遍歷
      for (int i=1;i<=n;i++)
      {
          scanf("%d",&a[i]);
          ans1[i]=query(a[i]+1,maxn,1,1,maxn,tree1);
          update (1,maxn,1,tree1,a[i]);
      }
      //反向遍歷
      for (int i=n;i>=1;i--)
      {
          ans2[i]=query(a[i]+1,maxn,1,1,maxn,tree2);
          update (1,maxn,1,tree2,a[i]);
      }
      long long int sum=0;
      //求總人數
      for (int i=1;i<=n;i++)
      {
          sum+=(ans1[i]*(n-i-ans2[i])+ans2[i]*(i-1-ans1[i]));
      }
      printf("%lld\n",sum);
    }
    return 0;
}