《树状数组》 --/小朋友排队

树状数组

细节

  1. 在存入树状数组的时候数字最小为1,如果a[i]可以为0,那么a[i]++
  2. 在进行add操作时,i < N,这里的N开大,不然会导致tr[i]加的更新的数据失误
  3. 注意树状数组的下标是以什么为下标,数组大小不可开错

在这里插入图片描述

树状数组操作

  • add(x, k)表示将序列中第x个数加上k。

树状数组-add.png

void add(int x, int k)
{
    for(int i = x; i <= n; i += lowbit(i))
        t[i] += k;
}

以add(3, 5)为例:
在整棵树上维护这个值,需要一层一层向上找到父结点,并将这些结点上的t[x]值都加上k,这样保证计算区间和时的结果正确。时间复杂度为O(logn)

  • sum(x)表示将查询序列前x个数的和(类前缀和)

树状数组-ask.png

以ask(7)为例:
查询这个点的前缀和,需要从这个点向左上找到上一个结点,将加上其结点的值。向左上找到上一个结点,只需要将下标 x -= lowbit(x),例如 7 - lowbit(7) = 6。

int ask(int x)
{
    int sum = 0;
    for(int i = x; i; i -= lowbit(i))
        sum += t[i];
    return sum;
}

题目链接

https://www.acwing.com/problem/content/243/

题目描述

在这里插入图片描述

【树状数组】

#include <iostream>
#include <cstring> 
using namespace std;
const int N = 1000010;
typedef long long LL;
int tr[N], a[N];
LL res[N];
int n;

//先上模板树状数组 
int lowbit(int x) {
	return x & -x;
}

void add(int id, int x) {//注意这里是“N”而不是"n",不然会少数据 
	for(int i = id; i < N; i += lowbit(i))	tr[i] += x;
}

int sum(int x) {
	int res = 0;
	for(int i = x; i; i -= lowbit(i))	res += tr[i];
	return res;
}

int main() {
	cin >> n;
	for(int i = 1; i <= n; i ++)	cin >> a[i], a[i]++;
	//记录每个数字对应的逆序数总合 
	//左边比i大的 
	for(int i = 1; i <= n; i ++) {
		add(a[i], 1);
		res[i] = i - sum(a[i]);
	}
	memset(tr, 0, sizeof tr);
	//右边比i小的 
	for(int i = n; i; i --) {
		add(a[i], 1);
		res[i] += sum(a[i] - 1);
	} 
	LL ans = 0;
	for(int i = 1; i <= n; i ++)
		ans += (1 + res[i]) * (LL)res[i] / 2;
	
	cout << ans;
	return 0;
}

【暴力代码】

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 200010;
int li[N], ri[N], lx[N], rx[N];
int a[N];
int n;
int main() {
	int n;
	cin >> n;
	for(int i = 1; i <= n; i ++)  cin >> a[i];
	for(int i = 1; i <= n; i ++ ) 
		for(int j = 1; j < i; j ++) 
			if(a[j] < a[i])	li[i] ++;
			else if(a[j] > a[i]) lx[i] ++;
	for(int i = 1; i <= n; i ++ ) 
		for(int j = n; j > i; j --) 
			if(a[j] < a[i])	ri[i] ++;
			else if(a[j] > a[i]) rx[i] ++;
	
	LL v = 0, M = 0;
	for(int i = 1; i <= n; i ++)
		v += (LL)li[i] * ri[i], M += (LL)lx[i] * rx[i];
	cout << M << " "  << v;
	
	
	return 0;
}

【树状数组代码】

#include <iostream>
#include <cstring> 
using namespace std;
const int N = 200010;
typedef long long LL;
int a[N];
int n, m;
LL tr[N];	//树状数组存的一块的和 
int li[N], ri[N], lx[N], rx[N];
//上树状数组模板
int lowbit(int x) {
	return x & -x;
}

void add(int x, int d) {
	for(int i = x; i <= n; i += lowbit(i)) 
		tr[i] += d;
}

LL sum(int x) {	//区间前缀和 
	LL sum = 0;
	for(int i = x; i; i -= lowbit(i))
		sum += tr[i];
	return sum;
}
 
int main() {
	cin >> n;
	for(int i = 1; i <= n; i ++)	scanf("%d", &a[i]);
	for(int i = 1; i <= n; i ++) {
		li[i] = sum(a[i] - 1);
		lx[i] = sum(n) - sum(a[i]);
		add(a[i], 1);
	}
	memset(tr, 0, sizeof tr);
	for(int i = n; i; i --) {
		ri[i] = sum(a[i] - 1);
		rx[i] = sum(n) - sum(a[i]);
		add(a[i], 1);
	}
	LL resl = 0, resr = 0;
	for(int i = 1; i <= n; i ++) {
		resr += li[i] * (LL)ri[i];
		resl += lx[i] * (LL)rx[i];
	}
	cout << resl << " " << resr;
	
	return 0;
}

小朋友排队

题目链接

https://www.acwing.com/problem/content/1217/

题目描述

在这里插入图片描述

在这里插入图片描述

思路

统计每个数的逆序数和,然后对其做等差数列和相加

【暴力代码】

#include <iostream>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 100010;
LL cnt[N];
int n, h[N];

int main() {
    cin >> n;
    for(int i = 0; i < n; i++)  scanf("%d", &h[i]);
    for(int i = 0; i < n; i++) {
        int res = 0;
        for(int j = 0; j < i; j++)  
            if(h[j] > h[i])
                res++;
        
        for(int j = i + 1; j < n; j++)
            if(h[j] < h[i])
                res++;
        
        cnt[i] = res;
    }
    
    LL ans = 0;
    for(int i = 0; i < n; i++) {
        ans += (1 + cnt[i]) * cnt[i] / 2;
    }
    cout << ans;
    
    return 0;
}

【树状数组】

#include <iostream>
#include <cstring> 
using namespace std;
//这里N为1e6 + 10,因为树状数组以身高为下标
const int N = 1000010;
typedef long long LL;
int tr[N], a[N];
LL res[N];
int n;

//先上模板树状数组 
int lowbit(int x) {
	return x & -x;
}

void add(int id, int x) {//注意这里是“N”而不是"n",不然会少数据 
	for(int i = id; i < N; i += lowbit(i))	tr[i] += x;
}

int sum(int x) {
	int res = 0;
	for(int i = x; i; i -= lowbit(i))	res += tr[i];
	return res;
}

int main() {
	cin >> n;
	for(int i = 1; i <= n; i ++)	cin >> a[i], a[i]++;
	//记录每个数字对应的逆序数总合 
	//左边比i大的 
	for(int i = 1; i <= n; i ++) {
		add(a[i], 1);
		res[i] = i - sum(a[i]);
	}
	memset(tr, 0, sizeof tr);
	//右边比i小的 
	for(int i = n; i; i --) {
		add(a[i], 1);
		res[i] += sum(a[i] - 1);
	} 
	LL ans = 0;
	for(int i = 1; i <= n; i ++)
		ans += (1 + res[i]) * (LL)res[i] / 2;
	
	cout << ans;
	return 0;
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值