首页 > 其他分享 >树状数组 Color the ball hdu 1556 线段树 洛谷p3372

树状数组 Color the ball hdu 1556 线段树 洛谷p3372

时间:2024-11-21 12:45:27浏览次数:3  
标签:hdu ball 洛谷 树状 int lowbit tree 差分 数组

目录

前言

树状数组

   lowbit函数

   直观表述 

      代码

      运行结果

树状数组构建代码

树状数组的应用

   单点修改和(单点)区间查询

   结合差分数组区间修改 ,单点查询

        差分数组

Color the ball hdu 1556

   问题描述

   问题分析

   代码

线段树 洛谷p3372

   问题描述

   问题分析        

   代码


前言

        树状数组和并查集一样,也是一个高级数据结构,编码简单,在算法竞赛中有着极其强大的应用。

  • 并查集主要解决的是集合的问题,而树状数组主要解决的则是有关区间的问题;
  • 并查集最本质的优化就是可以迅速找到元素所属的集合,树状数组则是可以在动态的数组中快速查询区间和。

树状数组

        树状数组,顾名思义,就是一个长得“很像”树的数组,本质上还是一个数组,但这个数组并不是常规的索引数组

   lowbit函数

        这个函数的功能是找到x的二进制数的最后一个1,换成十进制就表示能整除x的最大2的次幂

#define lowbit(x) ((x)&-(x))

   直观表述 

   下面我们来编写一个程序,直观表示下树状数组对应于数组的表示的元素

      代码

#include<iostream>
using namespace std;
const int N = 100005;

#define lowbit(x) ((x)&-(x))

int tree[N];

int main() {
	int n;
	cout << "输入树状数组的元素个数:";
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int a = lowbit(i);
		cout << "tree[" << i << "] = ";
		int k = i;
		while(a){
			if (a == 1) {
				cout << "a[" << k << "]";
			}
			else cout << "a[" << k << "] + ";
			k--;
			a--;
		}
		cout << endl;
	}
}

      运行结果

        那么我们根据这个结果试着构建一棵线段树:

树状数组构建代码

#define lowbit(x) ((x)&-(x))

int segment_tree[N + 1] = {0};  //注意tree【0】不使用

void update(int x, int d) {
	while (x <= N) {
		segment_tree[x] += d;  //更新其所属的根节点
		x += lowbit(x);
	}
}

int sum(int x) {
	int ans = 0;
	while (x > 0) {
		ans += segment_tree[x];
		x -= lowbit(x);
	}
	return ans;
}

树状数组的应用

        树状数组的操作都是直接作用在数组上的,树状数组只是辅助作用

   单点修改和(单点)区间查询

    

        根据这个树状数组,假设我们要更新a[2],那么相应的就需要更新tree[2],tree[4],tree[8]

会发现:

  • 2+lowbit(2)=4
  • 4+lowbit(4)=8

那么我们就可以得出单点修改的代码:

void update(int x, int d) {
	while (x <= N) {
		segment_tree[x] += d;  
		x += lowbit(x);
	}
}

        树状数组区间查询是利用前缀和实现的,如果要算sum(7),那么根据树状数组sum(7)=tree[7] + tree[6] + tree[4];会发现

  • 7-lowbit(7)=6
  • 6-lowbit(6)=4
  • 4-lowbit(4)=0

那么我们就可以得到前缀和的算法:

int sum(int x) {
	int ans = 0;
	while (x > 0) {
		ans += segment_tree[x];
		x -= lowbit(x);
	}
	return ans;
}

        根据前缀和我们就可以计算任意区间【L,R】的区间和:sum(L) - sum(R-1) ,实现单点查询m:sum(m) - sum(m-1)

   结合差分数组区间修改 ,单点查询

        差分数组

        会发现使用树状数组时如果要进行区间修改是一件非常耗时的事情,应为树状数组的单点修改不仅仅要修改单个点,还要修改与该点相关的所有父节点,但差分数组可以快速实现区间修改,如果不是在线操作,那么将不需要将区间中所有元素修改个遍,所以将树状数组和差分数组结合可以极大提高在动态数组中区间修改的效率。

        树状数组结合差分数组时,我们将a[]数组当成差分数组,tree[]数组仍然是辅助,那么在进行单点查询的时候只需要调用sum(i)函数即可知道i节点的修改信息。 这里我们借助一道例题理解。


Color the ball hdu 1556

hdu 1556

   问题描述

        有几个气球排成一排,每次选择一个区间对这个区间内的气球涂色,经过几次涂色后问这些气球被涂色的次数。

   问题分析

        问题分析之前先介绍下先介绍下算法编程中的两种情况“离线”情况,“强制在线”情况

  • 离线:是非交互的,一次读取很多信息,然后在对这些信息整合
  • 强制在线:是交互的,要求一问一答        

         很明显,本题是一个离线情况,有多次操作,但只在最后查询,如果是离线操作那么就可以使用差分数组,这是一个常见的离线型数据结构(这个没必要特别记忆,练多了也就没有离线和在线之分了),因为差分数组可以记录修改信息,然后根据前缀和操作处理这些信息。

        所以本题可以定义一个差分数组,先记录被涂色信息,然后在最后应用前缀和算法计算涂色信息。但本题其实可以利用树状数组优化,前面说过了,树状数组最大的优势就是可以迅速得到前缀和,

        细心的朋友可能注意到,树状数组的修改操作不仅仅需要修改一头一尾,还要修改与头尾有关的根结点,但是如果只用差分数组的话,修改操作只需要修改区间的一头一尾即可,确实这样来看修改操作好像并没有优化,反而更加复杂,但这些都是值得的,因为如果是差分数组求前缀和是需要一个一个加的,但树状数组由于前面的准备操作,使得求前缀和操作不需要一项一项加,而且修改操作是一劳永逸的,因为在进行前缀和操作时,会发现会不止一次用到修改操作带来的数据,所以一切都是值得的,而且树状数组+差分需要的内存和只用差分所需内存一样多。

        所以我们可以将原数组a[](这个数组没必要在程序中定义,事实上树状数组的叶子节点就是a[]数组,也可以发现其实并不是a[]数组中每个元素在树状数组中都有对应,虽然树状数组和a[]数组开的内存一样,但树状数组更多的时根节点,代表根节点下所有叶子节点的和) 当成一个差分数组,然后结合树状数组高效解决这个问题,要知道修改信息只需要求前缀和即可。

   代码

#include<iostream>
#include <cstring> 
using namespace std;
const int N = 100005;
#define lowbit(x) ((x)&-(x))

int segment_tree[N+1] = { 0 };

void update(int i, int d) {
	while (i <= N) {
		segment_tree[i] += d;
		i += lowbit(i);
	}
}

int sum(int x) {
	int ans = 0;
	while (x > 0) {
		ans += segment_tree[x];
		x -= lowbit(x);
	}
	return ans;
}

int main() {
	int n, l, r;
	while (cin >> n && n) {
		memset(segment_tree, 0, sizeof(segment_tree));
		for (int i = 1; i <= n; i++) {
			cin >> l >> r;
			//差分数组操作
			update(l, 1);
			update(r + 1, -1);
		}
		for (int i = 1; i <= n; i++) {
			//输出修改信息
			if (i != n) {
				cout << sum(i) << " ";
			}
			else {
				cout << sum(i) << endl;
			}
		}
	}
}

        有人肯定会问,你这也不是单点查询啊,如果这个sement_tree初始值不是0,你这不就炸了吗,好好好,满足你的要求,我们再看一道例题。

线段树 洛谷p3372

洛谷 p 3372

   问题描述

        问题很简单,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k。
  2. 求出某区间每一个数的和。

   问题分析        

        很容易想到,这题的暴力求解,但肯定会超时,可以使用线段树解决(之后发篇博客,先欠着),我们这里尝试用用树状数组+差分。但在此之前我们还需要做一些小学二年级的数学推倒,如果数组不是初始为0,那么只用一个差分数组是无法实现求前缀和的,因为一个树状数组只能算出每个元素的修改值,想到这里肯定有人反映过来了,我们求出变化量来加到原数组中不就行了吗,这个操作可以利用树状数组的update函数实现:

  • 也就是使用两个树状数组,
  • 一个用于求原数组中每个数的变化量,
  • 然后在另一个树状数组中利用update函数将这些变化量加到原数组上,
  • 然后再利用第二个树状数组的sum函数求前缀和

        不用数学推导也可以实现,这不失为一个方法,能想到说明你对树状数组有一定理解了,但这样操做会发现,编码特别麻烦,不信你可以试一试,这个操作不是全是修改,而是修改和查询操作有可能交替进行。所以还是推导一下好。

        我们已知差分就是前缀和的逆操作,那么我们就可以进行一下推导:

设原数组a[]的差分数组为d[]

那么前缀和:

sum(i) = a[1] + a[2] +......+a[i]

sum(i) = d[1] + ( d[1] + d[2] ) + ( d[1] + d[2] +d[3] )  + ... + ( d[1] + d[2] + ... +d[i] )

sum(i) = id[1] + (i-1)d[2] + ... + [i-(i-1)]d[i]

sum(i) = i ( d[1] + d[2] + ... +d[i] ) - ( 0d[1] + d[2] + 2d[3] + ... + (i-1)d[i] )

sum(i) = i \sum d[k] + \sum (k-1)d[k]     ( k=1~i )

        那我们就得到了一个公式:

sum(i) = i \sum d[k] + \sum (k-1)d[k]     ( k=1~i )

        那么我们就可以定义两个树状数组+差分数组一个表示d[k] ,另一个表示 (k-1)d[k]

        想要都得到区间[L,R]只需要sum(L)-sum(R-1)即可:

   代码


#include<iostream>
using namespace std;
#define ll long long
#define lowbit(x) ((x)&-(x))

const int N = 100005;

ll segment_tree1[N + 1] = { 0 };
ll segment_tree2[N + 1] = { 0 };

void update(int x, ll d, int v) {
	if (v == 1) {
		while (x <= N) {
			segment_tree1[x] += d;
			x += lowbit(x);
		}
	}
	else {
		while (x <= N) {
			segment_tree2[x] += d;
			x += lowbit(x);
		}
	}
}

ll sum(int x,int v) {
	ll ans = 0;
	if (v == 1) {
		while (x > 0) {
			ans += segment_tree1[x];
			x -= lowbit(x);
		}
	}
	else {
		while (x > 0) {
			ans += segment_tree2[x];
			x -= lowbit(x);
		}
	}
	return ans;
}

int main() {
	int n, m, c, R, L;
	ll e=0,old,v;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		old = e;
		cin >> e;
		update(i, e-old, 1);
		update(i, (i - 1) * (e - old), 2);
	}
	for (int i = 1; i <= m;i++) {
		cin >> c;
		if (c == 1) {
			cin >> L >> R >> v;
			update(L, v,1);
			update(R + 1, -v, 1);
			update(L, (L - 1) * v, 2);
			update(R + 1, -R * v, 2);
		}
		else {
			cin >> L >> R;
			cout << R * sum(R, 1) - sum(R, 2) - (L - 1) * sum(L - 1, 1) + sum(L - 1, 2) << endl;
		}
	}
	return 0;
}

 

标签:hdu,ball,洛谷,树状,int,lowbit,tree,差分,数组
From: https://blog.csdn.net/2301_80093604/article/details/143833616

相关文章

  • 【题解】洛谷P3531: [POI2012] LIT-Letters
    P3531[POI2012]LIT-Letters写了个假做法有点伤心,本以为是两个都求逆序对然后答案相减,但是这样在部分数据上确实合法,但是实际上毫无章法没有逻辑。正解考虑贪心,我们一个数字肯定要找最前面,第二次出现就去最前面第二次出现的位置,因为如果第一个A放在了后面,那么就有可能产生更对......
  • 洛谷P6225异或橙子
    洛谷P6225异或橙子位运算思维树状数组这是题面思路先看一下这个式子要干什么例如\(l=2,u=4\)的情况,记橙子序列\(A\)中第\(i\)个橙子的整数是\(a_i\),那么他要求的就是:\[a_2\oplusa_3\oplusa_4\oplus(a_2\oplusa_3)\oplus(a_3\oplusa_4)\oplus(a_2\oplusa_......
  • 洛谷算法题P1307 [NOIP2011 普及组] 数字反转
    题目题目描述给定一个整数N,请将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零(参见样例2)。输入格式一个整数N。输出格式一个整数,表示反转后的新数。样例#1样例输入#1123样例输......
  • 洛谷 P11210 强制在线动态二维数点
    题目传送门题目中的点满足\(y\lex\),那么不妨把每个点看成区间\([y,x]\)。那么题目相当于要求维护若干个区间,支持修改,以及查询询问区间中区间长度的最小值。从“区间长度的最小值”入手。显然包含别的区间的区间不能成为答案。排除了这样的区间,剩下区间按左端点升序排序,则右端......
  • 洛谷 P1613 跑路 做题记录
    前置芝士:最短路、floyd传递闭包、倍增思路看到题目里面的一次能走\(2^k\)千米,我们联想到倍增,因为只能用跑路器。我们枚举\(k\),然后做一次传递闭包,\((i,j)\)走\(2^k\)千米是相连的,当且仅当有一个点\(k\)是的\((i,k),(k,j)\)可以通过走\(2^{k-1}\)千米相连。此时,\((......
  • 【题解】洛谷:P8593 「KDOI-02」一个弹的投
    P8593「KDOI-02」一个弹的投物理题。首先你要搞懂什么时候会炮弹碰撞,结论:y坐标相同时,水平位置\(x_i\lex_j\)且落点满足\(d_i\ged_j\),两炮弹必然碰撞。但是为什么呢,像我这种完全没学高中物理的伪高中生就不会了,下落时每个物体的相对的高度差是不变的,因为根据伽利略运动独......
  • 【洛谷】P1914 小书童——凯撒密码
    题目背景某蒟蒻迷上了“小书童”,有一天登陆时忘记密码了(他没绑定邮箱or手机),于是便把问题抛给了神犇你。题目描述蒟蒻虽然忘记密码,但他还记得密码是由一个字符串组成。密码是由原文字符串(由不超过50个小写字母组成)中每个字母向后移动 n 位形成的。z 的下一个字母是 ......
  • 洛谷题单指南-二叉堆与树状数组-P2161 [SHOI2009] 会场预约
    原题链接:https://www.luogu.com.cn/problem/P2161题意解读:本题前面形式化描述已经足够清晰。解题思路:要判断线段之间是否有冲突(包含或者交叉),可以借助set,参考:https://www.cnblogs.com/jcwy/p/18447333只不过这里要统计冲突的数量,也就是允许相等的元素重复存在,可以借助multiset......
  • 洛谷:P1008 [NOIP1998 普及组] 三连击
    这道题需要我们找出所有符合要求的数对,由于数据量不大,这里我们可以使用枚举的方法进行枚举,那么我们从最小的三位数100到最大数999进行遍历寻找,再对这三个数进行判断,判断这三个数的每一位是否由1-9这9个数组成,且每个数只出现一次。在判断这个地方我们可以用一个数组来进行计数,将......
  • 洛谷题单指南-二叉堆与树状数组-P5677 [GZOI2017] 配对统计
    原题链接:https://www.luogu.com.cn/problem/P5677题意解读:所谓好的配对,通过分析公式∣ax−ay∣≤∣ax−ai∣(i≠x),可以得知就是一个ax与其差的绝对值最小的形成的配对,在数轴上就是距离ax最近的点ay,配对是下标(x,y),给定若干个区间[l,r],每个区间的配对数*区间编号的累加。解题思路:......