首页 > 其他分享 >[数据结构]Binary Indexed Trees(树状数组)

[数据结构]Binary Indexed Trees(树状数组)

时间:2023-06-25 21:12:33浏览次数:40  
标签:Binary 树状 int ll modify Trees 查询 long Indexed

Binary Indexed Trees(树状数组)

1.lowbit

lowbit(x)是x的二进制表达式中最低位的1所对应的值。比如,6的二进制是110,所以lowbit(6)=2。

lowbit(x) = x&(-x)

2.定义,查询,修改(eg1)

\(a1,a2,...,an\)

能在BZ的时间复杂度下完成:

  1. 单点加,\(ai+=d\)

  2. 查询前缀和\(\sum_{i = 1}^{x}ai\)

树状数组定义:

\(c_i = a_{i-lowbit(i)+1到i}\)的和

画个图...

image

修改和查询:

image

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 201000;

int a[N],n;
ll c[N];
ll query(int x){//1...x
	ll s = 0;
	for(;x;x-=x&(-x))
		s += c[x];
	return s;
}
void modify(int x,ll s){//a[x]+=s
	for(;x<=n;x+=x&(-x))
		c[x]+=s;
}

例题1.树状数组1

给n个数a1,a2,a3,…,an。

支持q个操作:

  1. 1 x d,修改\(ax=d\)。
  2. 2 x,查询\(\sum_{i=1}^{x}ai\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 201000;
int n,q;
int a[N];
ll c[N];
ll query(int x){//1...x
	ll s = 0;
	for(;x;x-=x&(-x))
		s += c[x];
	return s;
}
void modify(int x,ll s){//a[x]+=s
	for(;x<=n;x+=x&(-x))
		c[x]+=s;
}

int main()
{
	cin>>n>>q;
	for(int i = 1;i<=n;i++)
	{
		cin>>a[i];
		modify(i,a[i]);
	}
	for(int i = 1;i<=q;i++)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			int x,d;
			cin>>x>>d;
			modify(x,d-a[x]);
			a[x] = d;
		}
		else
		{
			int x;
			cin>>x;
			cout<<query(x)<<endl;
		}
	}
	return 0;
}

3.应用(eg2,3)

eg2.逆序对2

请问数组 \(a\) 的逆序对一共有多少个?形式化的说,请求出有多少组$ (i,j)\(满足\) i<j$ 并且 \(ai>aj\)。

思路:

  1. 扫描线思想,静态=>动态

    for(j = 1~n)

    ​ 统计\(a_1\)~\(a_{j-1}\)里面有多少个>\(a_j\)的

    我们有一个数据结构D,D里面存了\(a_1\)~\(a_{j-1}\)。

    问题变成了我们要统计D里面有多少个\(>=a_j\)的,统计完之后,我们把\(a_j\)加入D中。我们再动态的过程中不断把答案加起来,就是所有的逆序对数了。

    我们把一个静态的问题转化为一个动态带修改的问题。

  2. 对权值开树状数组

    对D里面,我们要做的是a.加入一个数 b.查询有多少个\(>=a_j\)的数

    比如我们加入一个数\(a_i\),那我们再\(D[a[i]]\)位置++

    要查询多少个\(>=a_j\)的数,即查询\(a_j\)后面又多少个1,实际上是做一个后缀查询。\(for(i = a_{j+1}\)~\(n)ans += D[i]\)

    总结:

    ①D[a[j]]+=1

    ②后缀查询

    这里跟树状数组没什么区别了,唯一区别就是前缀查询变成了后缀,我们把所有东西翻过来,就变成前缀和

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 201000;
int n;
int a[N];
ll c[N];
ll query(int x){//1...x
	ll s = 0;
	for(;x;x-=x&(-x))
		s += c[x];
	return s;
}
void modify(int x,ll s){//a[x]+=s
	for(;x<=n;x+=x&(-x))
		c[x]+=s;
}

int main()
{
	cin>>n;
	for(int i = 1;i<=n;i++)
	{
		cin>>a[i];
		a[i] = n+1-a[i];
	}
	ll ans = 0;
	for(int i = 1;i<=n;i++)
		ans += query(a[i]),modify(a[i],1);
	cout<<ans<<endl;
	return 0;
}

eg3.树状数组2

有\(n\)个数\(a1,a2,a3,…,an\)一开始都是0。

支持\(q\)个操作:

  1. 1 l r d,令所有的\(a_i(l≤i≤r)\)加上\(d\)。
  2. 2 x,查询\((\sum_{i=1}^{x}ai) \bmod 2^{64}\)。

操作:区间加+单点查询

差分:\(d_i = a_i - a_{i-1}\)

前缀和:\(a_i = d_1+d_2...+d_i\)

\([l,r]+1\)

\(d_l+=1,d_{r+1}-=1\)

查询单点其实就是个d的前缀和\(\sum_{i = 1}^{x}a_i\)

\(a_1 = d_1\)

\(a_2 = d_1+d_2\)

\(a_3 = d_1+d_2+d_3\)

那么\(a_1+a_2+a+3 = 3d_1+2d_2+d_3\)

那么$a_x = xd_1+(x-1)d_2+...+d_x = \sum_{i = 1}^{x}(x+1-i)*d_i $

化简一下$(x+1)(\sum_{i = 1}^{x}d_i)-(\sum_{i = 1}^{x}i*d_i) $

那么我们只需要维护出\(d_i\)的前缀和和\(i*d_i\)的前缀和就行了。这两个东西我们开两个树状数组取维护它。

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long u64;
const int N = 201000;
int n,q;
int a[N];
template<class T>
struct BIT
{
	T c[N];
	int size;
	void resize(int s){size = s;}
	T query(int x){//1...x
		assert(x<=size);
		T s = 0;
		for(;x;x-=x&(-x))
			s += c[x];
		return s;
	}
	void modify(int x,T s){//a[x]+=s
		assert(x!=0);//注意:树状数组下标不能是0,因为lowbit会死循环
		for(;x<=n;x+=x&(-x))
			c[x]+=s;
	}

};
BIT<u64>c1,c2;//c1:d[i],c2:i*d[i]

int main()
{
	cin>>n>>q;
	c1.resize(n),c2.resize(n);
	for(int i = 1;i<=q;i++)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			int l,r;
			u64 d;
			cin>>l>>r>>d;
			c1.modify(l,d);
			c1.modify(r+1,-d);
			c2.modify(l,l*d);
			c2.modify(r+1,(r+1)*(-d));
		}
		else
		{
			int x;
			cin>>x;
			u64 ans = (x+1)*c1.query(x)-c2.query(x);
			cout<<ans<<endl;
		}
	}
	return 0;
}

4.二分(eg4)

给\(n\)个数\(a1,a2,a3,…,an。\)

支持q个操作:

  1. 1 x d,修改\(a_x=d\)。
  2. 2 s,查询最大的\(T(0≤T≤n)\)满足\(\sum_{i=1}^{T}ai≤s\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 201000;
int n,q;
int a[N];
ll c[N];
/*
写法1:
ll query(ll s){
	ll t = 0;
	int pos = 0;

		// for(j = log(n)~0)
		// pos 是当前的位置
		// t 记录的是1~pos的和
		// pos' = pos+2^j

	for(int j = 18;j>=0;j--)
	{
		if(pos+(1<<j)<=n&&t+c[pos+(1<<j)]<=s)
		{
			pos += (1<<j);
			t+=c[pos];
		}
	}
	return pos;
}
*/
//写法2:
ll query(ll s){
	int pos = 0;
	for(int j = 18;j>=0;j--)
	{
		if(pos+(1<<j)<=n&&c[pos+(1<<j)]<=s)
		{
			pos += (1<<j);
			s-=c[pos];
		}
	}
	return pos;
}

void modify(int x,ll s){//a[x]+=s
	for(;x<=n;x+=x&(-x))
		c[x]+=s;
}

int main()
{
	cin>>n>>q;
	for(int i = 1;i<=n;i++)
	{
		cin>>a[i];
		modify(i,a[i]);
	}
	for(int i = 1;i<=q;i++)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			int x,d;
			cin>>x>>d;
			modify(x,d-a[x]);
			a[x] = d;
		}
		else
		{
			ll s;
			cin>>s;
			cout<<query(s)<<endl;
		}
	}
	return 0;
}

5.高维树状数组(eg5)

\(c[i][j]\)

\(a[i-lowbit[i]+1\)~\(i]\) \([j-lowbit[j]+1\)~\(j]\)

eg5.二维树状数组

给\(n×m\)个数\(a_{1,1},a_{1,2},a_{1,3},…,a_{1,m},…,a_{n,m}\)。

支持\(q\)个操作:

1 x y d,修改\(a_{x,y}=d\)。

2 x y,查询\(\sum_{i = 1}^{x}a_{i,j}\)。

时间复杂度\(O(log^2(n))\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 510;
int n,m,q;
int a[N][N];
ll c[N][N];
ll query(int x,int y){
	ll s = 0;
	for(int p = x;p;p-=p&(-p))
		for(int q = y;q;q-=q&(-q))
			s += c[p][q];
	return s;
}

void modify(int x,int y,ll s){
	for(int p = x;p<=n;p+=p&(-p))
		for(int q = y;q<=m;q+=q&(-q))
			c[p][q]+=s;
}

int main()
{
	cin>>n>>m>>q;
	for(int i = 1;i<=n;i++)
	{
		for(int j = 1;j<=m;j++)
		{
			cin>>a[i][j];
			modify(i,j,a[i][j]);
		}
	}
	for(int i = 1;i<=q;i++)
	{
		int op;
		cin>>op;
		if(op==1)
		{
			int x,y,d;
			cin>>x>>y>>d;
			modify(x,y,d-a[x][y]);
			a[x][y] = d;
		}
		else
		{
			int x,y;
			cin>>x>>y;
			cout<<query(x,y)<<endl;
		}
	}
	return 0;
}

标签:Binary,树状,int,ll,modify,Trees,查询,long,Indexed
From: https://www.cnblogs.com/nannandbk/p/17503960.html

相关文章

  • Codeforces Round 875 (Div. 2) C. Copil Copac Draws Trees
    bfs解法如果是暴力求解的话就每次都扫描一次所有边直到所有点都和树连接优化:每次扫描我们可以发现会重复扫描那些已经存在树中的边了,因此我们可以只扫描还没有存在树中的边且是没扫过的边对于每次更新,比如由点a已经在树中,更新点b,我们只需判断点a被更新到树中点的编号和a-b边的......
  • TreeSaver.js 的工作流程逻辑
    Treesaver是浏览器大小尺寸敏感(size-sensitive)的,会就着当前的浏览器尺寸(browsersize),选用不同的分栏表格(grid)做排版。初始化TreeSaver.js框架的入口源代码在后面可以看到:https://github.com/Treesaver/treesaver/blob/master/src/init.js这里的代码用到了Google开发的JS库:Closur......
  • Win7环境下TreeSaver编译环境的搭配
    首先你需要先搭配出”Win7环境下TreeSaver例子环境的搭配”然后才能继续下一步编译环境。例子环境搭配后,你可以在源代码目录下执行paver命令,搭配例子测试环境,也可以执行paverdebug生成带调试注释信息的treesaver脚本,当然也可以使用paverclean删除生成的文件。这些可以......
  • Binary Ninja (二进制忍者)简介
    https://binary.ninja/专为新手打造的反汇编器 第一印象     软件简介虽然IDA在反汇编器的地位无人可以撼动,但是新手使用IDA往往也会感到无所适从。BinaryNinja以其精美的界面和便捷的交互方式,使得新人简单了解后就能很快上手。运行平台:支持windows、linux,macos。具体来说,L......
  • TreeSet
    TreeSet的使用下面是TreeSet的方法使用,代码实现如下:publicstaticvoidmain(String[]args){ TreeSet<String>set=newTreeSet<>(); //添加元素 set.add("小希"); set.add("小空"); set.add("小丽"); set.add("小光"); //获取元素......
  • 利用Binary Hash Codes的深度图像检索
    1.概述本文的重点:图像的binaryhashcode的生成方法两阶段的检索方法——coarse-to-finesearchstrategy2.基于内容的图像检索2.1.基于内容的图像检索基于内容的图像检索(Content-basedImageRetrieval,CBIR)旨在通过对图像内容的分析搜索出相似的图像,其主要的工作有如下两点:图像......
  • 2023年6月11日,TreeSet,Comparable,HashMap
    1.Set1.TreeSetTreeSet1、存储Integer的元素,升序排列2、存储String的元素,字典排列TreeSet根据元素的不同类型使用不同的排序规则publicclasstest01{/***知识点:TreeSet*1、存储Integer的元素,升序排列*2、存储String的元素,字典排列*......
  • 算法 in Go:Binary Search(二分查找)
    算法inGo:BinarySearch(二分查找)BinarySearch(二分查找)BinarySearch(二分查找)猜数1、2、3、4、5、6、7、8排好序一个集合,先从中间开始猜,根据提示就可以排除一半,在剩余的一半里,再从中间开始猜,依此类推,这就是二分查找。BinarySearch(二分查找)接收什么参数,返回什么值输入:......
  • 【Windows】TreeSoft数据库管理系统 TreeDMS 和 TreeNMS
    官方地址:http://www.treesoft.cn/dms.html#learningTreeSoft数据库管理系统TreeDMS支持MySQL,MariaDB,Oracle,PostgreSQL,SQLServer,DB2,MongoDB,Hive,SAPHANA,Sybase,Caché,Informix,Impala,ElasticSearch,clickHouse,cassandra,AmazonRedshift,达梦DM,金仓Kin......
  • Set系列集合:TreeSet集合
                 ......