PingPang

时隔好久,终于隐隐约约搞懂了树状数组。

树状数组原来就不大会用,这道题也是似是而非。终于懂了就写写吧。

题目大意:

 一条大街上住着n个乒乓球爱好者,他们经常组织乒乓球比赛且每个人的能力值ai都不同.每次比赛需要2个比赛者和一个裁判,他们有一个奇怪的规定:当裁判的那个人必须住在这两个比赛者之间,且裁判的能力值也必须在这两个人之间.问一共有多少种比赛组织方式.

        输入:首先是T(1<=T<=20),表示实例个数.对于每个实例第一行是一个n(3<=n<=20000),然后是n个不同的整数即a1,a2…an(1<=ai<=100000),按照他们的住所位置从左到右给出每个人的能力值.

        输出:比赛组织的总数.

思路:假设位置i,在[1,i-1]区间,比i这个人能力小的有ss个,那么这个区间比i能力大的人有(i-1)-ss个;

         在【i+1,n】区间,比i这个人能力小的有ff个,那么在这个区间比i能力大的人有(n-i)-ff个

          那么假设i是裁判,能裁定的方式有ss*((n-i)-ff)+((i-1)-ss)*ff个

         然后所有(1~n)加和即可。

**难点在于,如何快速的找出比这个人能力小的人的个数来。

这时候就要想树状数组的真实含义了。

剖析树状数组:

树状数组不过就是巧妙地利用一个数组c[i],还有两个函数,add和sum


如图,它的意思就是每次最小的更改几个c变化要变化的c。

这道题呢,要把下标1~n改为改为a[i]能力值,为什么可以这样呢,因为树状数组在求和时的sum函数是返回的a【1】~a【n】的和,我们用能力值作为下标,如果这个数存在就+1,标记有这个数。那么标记前求和一次,返回的就是比他小的个数的数量,因为一开始输入的能力值虽然不是有序的,但是在树状数组内求和的时候是有序的(从小到大),因为能力值大的在后边。举个例子,输入的能力值为4 5 3 6,那么第一个数的时候执行一次求和,显然这个时候5  3  6都还没有标记,所以为0.然后标记C【4】++(用能力值作为下标),然后到5,先执行一次求和,显然答案就为1,之后C【5】++,到了3,先求一次和,因为树状数组返回的是c[1]+c[2]+c[3],那么显然答案为0,然后C【3】++,到了6,执行求和,答案显然为3,然后标记。。。。这个问题就完美解决了。。。。

正着找一边,然后c数组清空,再反着找一遍。

代码如下:

#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <map>
#include <string>
#include <set>
#include <ctime>
#include <cmath>
#include <cctype>
using namespace std;
#define maxn 20000+10
#define maxnn 100000+10
#define LL long long
int cas=1,T;
int a[maxn];
int L[maxn];
int R[maxn];
int c[maxnn];
int maxx;
int lowbit(int i)
{
	return i&(-i);
}
int sum(int i)
{
	int ans = 0;
	while (i)
	{
		ans +=c[i];
		i-=lowbit(i);
	}
	return ans;
}
void add(int i,int d)
{
	while (i<=maxx)
	{
		c[i]+=d;
		i+=lowbit(i);
	}
}

int main()
{
	//freopen("in","r",stdin);
	scanf("%d",&T);
	while (T--)
	{
		int n;
		scanf("%d",&n);
		memset(a,0,sizeof(a));
		maxx=0;
		for (int i = 1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			maxx = max(maxx,a[i]);
		}
        memset(L,0,sizeof(L));
		memset(R,0,sizeof(R));
		memset(c,0,sizeof(c));
		for (int i = 1;i<=n;i++)
		{
			L[i] = sum(a[i]);
			add(a[i],1);
		}
		memset(c,0,sizeof(c));
		for (int i = n ;i>=1;i--)
		{
			R[i] = sum(a[i]);
			add(a[i],1);
		}
		LL ans = 0;
		for (int i = 1;i<=n;i++)
		{
            ans += ( (LL)(L[i]*(n-i-R[i])) + (LL)(R[i]*(i-L[i]-1)) );
		}
		printf("%lld\n",ans);
	}
	//printf("time=%.3lf",(double)clock()/CLOCKS_PER_SEC);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值