树状数组(二叉索引树)

目录

一,树状数组

1,树状数组

2,区间更新单点查询

3,离散化

二,模板代码

三,OJ实战

CodeForces 706B Interesting drink

CSU 1770 按钮控制彩灯实验

HDU 1166 敌兵布阵

POJ 2309 BST

POJ 2352 HDU 1541 Stars

力扣 307. 区域和检索 - 数组可修改

力扣 2250. 统计包含每个点的矩形数目(离散化)

四,二维树状数组

1,模板代码

2,OJ实战

POJ 1195 Mobile phones

POJ 2155 Matrix (区间更新单点查询)

力扣 2536. 子矩阵元素加 1(区间更新单点查询)

力扣 308. 二维区域和检索 - 矩阵可修改(单点更新区间查询)

五,支持在动态增长的数组中查询前缀最大值的树状数组


一,树状数组

1,树状数组

树状数组,也叫二叉索引树(Binary Indexed Tree)

如图,A是基本数组,C是求和数组,

其中,C[1]=A[1], C[2]=C[1]+A[2], C[3]=A[3], C[4]=C[2]+C[3]+A[4]......C[8]=C[4]+C[6]+C[7]+A[8]......

树状数组最简单最经典的使用场景,就是单点更新区间查询。

2,区间更新单点查询

对于一段区间内增加某个值的操作,可以利用差分,转换成单点更新,把单点更新,转换成前缀和查询。

3,离散化

参考离散化

二,模板代码

#include<iostream>
#include<string.h>
using namespace std;

template<int maxLen = 100000>
class TreeArray
{
public:
	TreeArray(int len)//len是元素实际数量,元素id范围是[1,n]
	{
		this->n = len;
		memset(c, 0, sizeof(int)*(len + 1));
	}
	void add(int i, int x)
	{
		while (i <= n)
		{
			c[i] += x;
			i += (i&(-i));
		}
	}
	int getSum(int i)
	{
		int s = 0;
		while (i)
		{
			s += c[i];
			i -= (i&(-i));
		}
		return s;
	}
private:
	int n;
	int c[maxLen+5];
};

三,OJ实战

CodeForces 706B Interesting drink

题目:

Description

Vasiliy likes to rest after a hard work, so you may often meet him in some bar nearby. As all programmers do, he loves the famous drink "Beecola", which can be bought in n different shops in the city. It's known that the price of one bottle in the shop i is equal to xi coins.

Vasiliy plans to buy his favorite drink for q consecutive days. He knows, that on the i-th day he will be able to spent mi coins. Now, for each of the days he want to know in how many different shops he can buy a bottle of "Beecola".

Input

The first line of the input contains a single integer n (1 ≤ n ≤ 100 000) — the number of shops in the city that sell Vasiliy's favourite drink.

The second line contains n integers xi (1 ≤ xi ≤ 100 000) — prices of the bottles of the drink in the i-th shop.

The third line contains a single integer q (1 ≤ q ≤ 100 000) — the number of days Vasiliy plans to buy the drink.

Then follow q lines each containing one integer mi (1 ≤ mi ≤ 109) — the number of coins Vasiliy can spent on the i-th day.

Output

Print q integers. The i-th of them should be equal to the number of shops where Vasiliy will be able to buy a bottle of the drink on the i-th day.

Sample Input

Input
5
3 10 8 6 11
4
1
10
3
11
Output
0
4
1
5

这个题目就是,给出若干查询,对于每个查询,输出数组中有多少个数比它小。

这不就是最典型的树状数组吗?

代码:
 


#include <iostream>
#include <string.h>
using namespace std;



int main()
{
	int nn, a, q;
	scanf("%d", &nn);
	TreeArray<100000> opt(100000);
	for (int i = 0; i < nn; i++)
	{
		scanf("%d", &a);
		opt.add(a, 1);
	}
	scanf("%d", &q);
	for (int i = 0; i < q; i++)
	{
		scanf("%d", &a);
		if (a >= 100000)printf("%d\n", opt.getSum(100000));
		else printf("%d\n", opt.getSum(a));
	}
	return 0;
}

CSU 1770 按钮控制彩灯实验

题目:


Description

应教学安排,yy又去开心的做电学实验了。实验的内容分外的简单一串按钮通过编程了的EEPROM可以控制一串彩灯。然而选择了最low的一种一对一的控制模式,并很快按照实验指导书做完实验的yy马上感觉到十分无趣。于是他手指在一排按钮上无聊的滑来滑去,对应的彩灯也不断的变化着开关。已知每一个按钮按下会改变对应一个彩灯的状态,如此每次yy滑动都会改变一串彩灯的状态。现已知彩灯最初的状态,以及yy n次无聊的滑动的起点和终点l,r。现问彩灯最终的状态。

Input

有多组数据。
每组数据第一行,n(1<=n<=10^5)代表彩灯串长度,t(0<=t<=10^5)代表yy滑动的次数
第二行n个数(0表示灭1表示亮)给出n个彩灯的目前的状态。
之后t行每行两个数li,ri(1<=li<=ri<=n)代表每次滑动的区间。

Output

每组用一行输出最终的串的状态,格式见样例。

Sample Input

3 2
1 0 1
1 3
2 3
Sample Output

0 0 1

首先说下这个题目的思路。

肯定不能用暴力的θ(n*t)的方法。

很明显,这个题目是有规律的。

在区间左侧和区间右侧都没有被改变,只有区间中间的彩灯被改变了。

而且这个题目最后只需要判断奇偶性,所以彩灯被改变了多少次,就是直接等于有多少个区间端点在它的左侧。

例如,本题的2个区间是【1,3】【2,3】,

彩灯1的左侧有1个端点,彩灯2的左侧有2个端点,彩灯3的左侧有0个端点

注意!计算左侧的端点数量的时候,区间左端点刚好等于彩灯的情况是要算进去的,

但是区间右端点刚好等于彩灯的情况是不能算进去的。

再比如一个任意的例子,

8 1

0 0 0 0 0 0 0 0

3 6

很明显答案应该是0 0 1 1 1 1 0 0

要说这个例子找规律应该不难。第1、2个彩灯左边有0个区间端点,

第3、4、5、6个区间端点左边有1个端点,第7、8个彩灯左边有2个端点。

重复一遍:彩灯3计入了左端点3,彩灯6不计入右端点6。

其实还有一种思路:区间【3,6】这个操作可以分解成2个操作,

1,改变前6个彩灯的状态,2,改变前3个彩灯的状态。

讲道理,这个想法应该更自然,而且更容易想到。

不过思考无非就是平时的积累(装逼了哈哈哈哈,其实就是碰巧玩过一个类似的游戏,点亮所有的灯)加上关键时刻的灵感,

看到这个题目的时候,我的灵感就是数端点数目,

仔细一想才有了这种把操作分解的想法。

这个题目的操作是无序而且互相独立的,所以只需要统计每个彩灯的左边一共出现过多少端点就可以了。

所以这个题目有2种方案,都AC了。

第一种:树状数组。

代码:

#include<iostream>
using namespace std;


int list__[100001];
int main()
{
	int n,t, a;
	while (cin >> n >> t)
	{
		TreeArray opt(n);
		for (int i = 1; i <= n; i++)
		{
			cin >> list__[i];
		}
		while (t--)
		{
			cin >> a;
			opt.add(a, 1);
			cin >> a;
			opt.add(a, 1);
		}
		for (int i = 1; i <= n; i++)
		{
			cout << (list__[i] + opt.getSum(i)) % 2;
			if (i < n)cout << " ";
		}
		cout << endl;
	}
	return 0;
}

我把关闭同步的语句注释掉了,否则直接runtime error。。。。。。

然而,这个题目有一个更简单的方法,不需要任何数据结构,而且还略高效一点点。

因为这个题目就是恰好n次查询,所以用树状数组不如直接用最普通的数组了。

代码:

#include<iostream>
using namespace std;
 
int n;
int list[100001];	
int change[100002];
 
int main()
{
	int t, a;
	while (cin >> n >> t)
	{
		for (int i = 1; i <= n; i++)
		{
			cin >> list[i];
			change[i] = 0;
		}
		while (t--)
		{
			cin >> a;
			change[a]++;
			cin >> a;
			change[a + 1]++;
		}
		change[0] = 0;
		for (int i = 1; i <= n; i++)
		{
			change[i] += change[i - 1];
			cout << (list[i] + change[i]) % 2;
			if (i < n)cout << " "
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值