LA4329(树状数组)

博客内容讲述了如何利用树状数组解决一个关于乒乓球比赛裁判问题的算法。比赛中,每三名选手组成一队,中间选手的能力值需介于其他两人之间。通过分析,得出答案需求解每个选手作为裁判的合法比赛次数,即C[i]和D[i]。博客详细介绍了树状数组的概念,以及如何使用树状数组进行前缀和查询和修改操作,以计算C[i]和D[i],并提供了求解lowbit的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  题目的大致意思是:有n个乒乓球手,然后他们都有一个能力值,然后需要每三个选手可以组成一场比赛,这场比赛要求,中间的那个人作为裁判,而且中间的人的能力值必须在两位选手之间,这个比赛才是合法的比赛,问这个这些选手一共能组织成多少场比赛?

  解题思路:首先对单个人作为裁判进行研究,假设标号i的人,假设前面小于他能力值的人为C[i],那么前面大于他能力值的即为i - 1 - C[i],假设后面小于他能力值的人为D[i],那么后面大于他能力值的即为n - i - D[i],根据乘法原理和加法原理,题目的答案即为:

  

  所以最终题目即转化为求C[i], D[i].

   

  在求C[i],D[i]之前,先引入树状数组的知识,有如下的图:

   

   建立一个lowbit数组我们构建lowbit[i] = j,j是i中二进制位上从右往左数,第一个为1的位置所代表的数(例如lowbit[20] = 4, 因为(20) = 10100, lowbit[20] = 100 = 4)。

   从而我们可以定义数组C的意义:

   

   即为前缀和。

   那么对于两个操作,一个是修改数组中某一个值,一个是查询区间(L,R)的和,是树状数组的两个基本操作,对于求和,刚才已经介绍了前缀和,我们可以发现:

   

   而对于修改操作,我们同样可以得到:

   

   d为该位置上A[X]的改变值。

   

   对于题目中C[i],D[i]的求解,我们还需要转化一下模型才能回到树状数组上:

   我们对于其能力值进行研究,对于数组A[i]代表能力值为i的选手是否存在,如果A[i]=1代表存在,A[i] = 0代表不存在。对于每一个选手它的能力值我们就动态的加入其中,要求它的C[i]即为求加入后的前缀和,所以最早的初始值A都为0,没加入一个选手就领A[num[i]] = 1,所以其为树状数组的标准操作。求D[i]也是同理的,不过就是要反向操作。

  

   另外对于lowbit的求解有,lowbit[i] = (i & (-i)),用之前的定义叙述的找,竟然会让它超时,难以想象。

  

#include "stdio.h"
#include "string.h"
#include "math.h"
#include <string>
#include <queue>
#include <stack>
#include <vector>
#include <map>
#include <algorithm>
#include <iostream>
using namespace std;

#define MAXM 1
#define MAXN 1
#define max(a,b) a > b ? a : b
#define min(a,b) a < b ? a : b
#define Mem(a,b) memset(a,b,sizeof(a))
int Mod = 1000000007;
double pi = acos(-1.0);
double eps = 1e-6;

typedef struct{
	int f,t,w,next;
}Edge;
Edge edge[MAXM];
int head[MAXN];
int kNum;
typedef long long LL;

void addEdge(int f, int t, int w)
{
	edge[kNum].f = f;
	edge[kNum].t = t;
	edge[kNum].w = w;
	edge[kNum].next = head[f];
	head[f] = kNum ++;
}

int n, T;
int lowbit[100005];
int pC[100005];
int pD[100005];
int num[100005];
int X[100005];

void init(){
	lowbit[0] = 0;
	for(int i = 1; i <= 100000; i ++){
		lowbit[i] = (i & (-i));
	}
}

void Add(int x){
	while(x <= 100000){
		X[x] += 1;
		x += lowbit[x];
	}
}

int Sum(int x){
	int sum = 0;
	while(x){
		sum += X[x];
		x -= lowbit[x];
	}
	return sum;
}


void solve(){
	Mem(X, 0);
	for(int i = 1; i <= n; i ++){
		scanf("%d", &num[i]);
	}

	for(int i = 1; i <= n; i ++){
		pC[i] = Sum(num[i]);
		Add(num[i]);
	}

	Mem(X, 0);
	for(int i = n; i > 0; i --){
		pD[i] = Sum(num[i]);
		Add(num[i]);
	}

	LL sum = 0;
	for(int i = 1; i <= n; i ++){
		sum += (1LL * pC[i] * (n - i - pD[i]) ) + (1LL * (i - pC[i] - 1) * pD[i] );
	}
	cout<<sum<<endl;

}

int main()
{
//	freopen("d:\\test.txt", "r", stdin);
	init();
	while(cin>>T){
		while(T--){
			cin>>n;
			solve();
		}
	}

	return 0;
}


   

  














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值