[UVALive - 4329] Ping pong 树状数组入门

题目链接:Ping pong
题意

给你n个数,你从中取3个数,要求中间的数字大小在两边数字之间。问你总共有多少种取法。

题解

这个题首先需要分析转化。

假设第i个人作为中间数
a 1 ~ a i − 1 有 c i 个 数 小 于 a i , 那 么 有 ( i − 1 − c i ) 个 数 大 于 a i ; {a_1~a_{i-1}有c_i个数小于a_i,那么有(i-1-c_i)个数大于a_i;} a1ai1ciai(i1ci)ai
a i + 1 ~ a n 有 d i 个 数 小 于 a i , 那 么 有 ( n − i − d i ) 个 数 大 于 a i {a_{i+1}~a_n有d_i个数小于a_i,那么有(n-i-d_i)个数大于a_i} ai+1andiai(nidi)ai

那么 a n s = ∑ i = 1 n ( c i ∗ ( n − i − d i ) + d i ∗ ( i − 1 − c i ) ) {ans=\sum_{i=1}^n(c_i*(n-i-d_i)+d_i*(i-1-c_i))} ans=i=1n(ci(nidi)+di(i1ci))

显而易见,这个题就可以转化为求解 c i 和 d i 。 {c_i和d_i。} cidi

由于本题对位置有要求所以无法排序,如果暴力的话,你需要 O ( n 2 ) {O(n^2)} O(n2)的时间复杂度,很明显会超时。此时可以用树状数组来维护一个以 a i {a_i} ai为下标的二叉索引树x,那么x[a[i]]代表a[i]出现的次数,此时 c i = x 1 + x 2 + . . . . + x i − 1 {c_i=x_1+x_2+....+x_{i-1}} ci=x1+x2+....+xi1,每遇到一个 a i a_i ai,就add(a[i],1)。同理 d i {d_i} di就倒着遍历计算即可。树状数组计算前缀和的时间复杂度为 O ( l o g r ) r 为 a i 的 上 限 {O(logr)r为a_i的上限} O(logr)rai,所以总体时间复杂度为 O ( n l o g r ) {O(nlogr)} O(nlogr)

代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<cassert>
#include<cctype>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<deque>
#include<iomanip>
#include<list>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
using namespace std;
//extern "C"{void *__dso_handle=0;}
typedef long long ll;
typedef long double ld;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define lowbit(x) x&(-x)

const double PI=acos(-1.0);
const double eps=1e-6;
const ll mod=1e9+7;
const int inf=0x3f3f3f3f;
const int maxn=1e5+10;
const int maxm=2e5+10;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

int a[maxn],n,c[maxn],d[maxn];
int x[maxm];
int sum(int d)
{
	ll sum=0;
	while(d>=1) {
		sum+=x[d]; d-=lowbit(d);
	}
	return sum;
}
void add(int d) {
	while(d<=100000) {
		x[d]+=1;
		d+=lowbit(d);
	}
}
int main()
{
	int t;
	cin >> t;
	while(t--)
	{
		cin >> n;
		for(int i=1;i<=n;i++) cin >> a[i];
		memset(x, 0, sizeof(x));
		for(int i=1;i<=n;i++)
		{
			c[i]=sum(a[i]);
			add(a[i]);
		}
		memset(x, 0, sizeof(x));
		for(int i=n;i>=1;i--)
		{
			d[i]=sum(a[i]);
			add(a[i]);
		}
		ll sum=0;
		for(int i=1;i<=n;i++)
			sum+=c[i]*(n-i-d[i])+d[i]*(i-1-c[i]);
		cout << sum << endl;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值