A - Ping pong(树状数组+顺序对)

Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 32768/32768K (Java/Other)
Total Submission(s) : 81   Accepted Submission(s) : 27
Problem 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个乒乓球选手,住在一条直线上,从左到右依次输入。每个选手有一个技术等级值。他们想打比赛,就要找裁判,为了省时和公正,选裁判的条件为裁判的位置和技术等级都在两位选手之间,求能打多少场比赛。

思路:

虽然不是第一次接触树状数组,但是做到这个题的时候,第一时间也还是想不通怎么把树状数组用上的,一开始用的暴力,肯定超时了,后来请教大神,终于领悟了一点点,就是不用仔细去研究树状数组是怎样构成的,就把他看作可以求区间和的一个东西就行。

总的思路就是对于除了最左边和最右边两个数之外,对于之间这部分数求每个数左边小于它的数的个数minl[i]乘以右边大于它的数的个数加上每个数左边大于它的个数乘以右边小于它的个数minr[i],然后全部求和。核心在于怎样求minl[i]和minr[i]两个数组。

先将C数组初始化为0,将输入的每个数值作为下标带进树状数组,求1-a[i]的区间和(minl[i]),即1的数量就是a[i]左边小于他的数的个数,每求完一个区间和就在这个位置上加1,表示这个位置有数了,然后按照这个规律循环一遍就可以把所有数的左边小于它的个数给求出来,然后在反过来遍历一遍,就可以把每个数右边小于它的数的个数给求出来(minr[i])。

比如说 数列为:5 4 3 6 8

minl[1]=sum[5];  sum[5]=0+0+0+0+0=0;

然后5的位置上加一。此时c数组为:0 0 0 0 1 0

minl[2]=sum[4]; sum[4]=0+0+0+0=0;

然后4的位置上加一。此时c数组为:0 0 0 1 1 0

minl[3]=sum[3];sum[3]=0+0+0=0;

然后4的位置上加一。此时c数组为:0 0 1 1 1 0

minl[4]=sum[6]; sum[6]=0+0+1+1+1+0=3;

然后6的位置上加一。此时c数组为:0 0 1 1 1 1

minl[5]=sum[8]; sum[8]=0+0+1+1+1+1+0+0=4;

然后8的位置上加一。此时c数组为:0 0 1 1 1 1 0 0 1

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<set>
#include<queue>
#include<stack>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const double PI = 4*atan(1.0);
const int maxm = 1e8+5;
const int maxn = 2e5+5;
const int INF = 0x3f3f3f3f;
const ll LINF = 1ll<<62;
const int MAXN=100005;

int a[MAXN],c[MAXN],minr[MAXN],minl[MAXN];

int lowbit(int i)
{
    return i&(-i);
}

void update(int pos,int num)
{
    while(pos<MAXN)
    {
        c[pos]+=num;
        pos+=lowbit(pos);
    }
}

int sum(int pos)
{
    int s=0;
    while(pos>0)
    {
        s+=c[pos];
        pos-=lowbit(pos);
    }
    return s;
}

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        cin>>n;
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            minl[i]=sum(a[i]);
            update(a[i],1);
        }
        memset(c,0,sizeof(c));
        for(int i=n;i>=1;i--)
        {
            minr[i]=sum(a[i]);
            update(a[i],1);
        }
        ll res=0;
        for(int i=1;i<=n;i++)
        {
            res+=minl[i]*(n-i-minr[i])+minr[i]*(i-1-minl[i]);
        }
        cout<<res<<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zhouchenghao123/article/details/84344499