数据结构·树状数组

时过两年,我!终于理解树状数组了!
在这贴上让我恍然大明白的b站视频链接

树状数组中的小问题
单点修改的时候注意d不要为0
不然就会出现加的lowbit一直为0导致死循环的情况
(sxht dalao居然一言道出问题所在 %%%)

树状数组

树状数组可以高效地完成单点查询和区间修改。

剩下的有时间补,贴上修改和查询的代码↓

int lowbit(int x) { return x&-x; }
void _update(int d,int x)
{
	while (d<=N) t[d]+=x,d+=lowbit(d);
}
int _query(int x)
{
	int res=0;
	while (x)
	{
		res+=t[x];
		x-=lowbit(x);
	}
	return res;
}

Ehhh Ah


【YbtOj】例题

A.单点修改区间查询

树状数组板子题(逃

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,q;
int a[N];
int t[N];

int lowbit(int x) { return x&-x; }
void _update(int d,int x)
{
	while (d<=N)
	{
		t[d]+=x;
		d+=lowbit(d);
	}
}
int _query(int x)
{
	int res=0;
	while (x)
	{
		res+=t[x];
		x-=lowbit(x);
	}
	return res;
}
signed main()
{
	scanf("%lld%lld",&n,&q);
	for (int i=1;i<=n;i++) 
	{
		scanf("%lld",&a[i]);
		_update(i,a[i]);
	}
	while (q--)
	{
		int op,x,y;
		scanf("%lld%lld%lld",&op,&x,&y);
		if (op==1) _update(x,y);
		else printf("%lld\n",_query(y)-_query(x-1));
	}
	return 0;
}

B.逆序对

显然,以\(a_{i}\)为第一个元素的逆序对的数量就是 \(j\in [i+1,n],a_{i}>a_{j}\) 的数量,所以只需要统计出每个 \(i\) 后值比 \(a_{i}\) 小的 \(j\) 的数量,它们的和就是最终答案。

于是乎,这就相当于是一个“权值树状数组”,每次查询比 \(a_{i}\) 小的值的数量,注意倒序扫描。因为值域较大,所以先离散化就好了

#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n;
struct node{
	int x,d;
}a[N];
int rk[N];
int t[N];
int cnt;

int lowbit (int x) { return x&-x; }
bool cmp(node x,node y) 
{
	if (x.x==y.x) return x.d<y.d;
  	return x.x<y.x;  
}
void _update(int d,int x)
{
	while (d<=N)
	{
		t[d]+=x;
		d+=lowbit(d);
	}
}
int _sum(int x)
{
	int res=0;
	while (x)
	{
		res+=t[x];
		x-=lowbit(x);
	}
	return res;
}
signed main()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++) 
	{
		scanf("%lld",&a[i].x);
		a[i].d=i;
	}
	
	sort(a+1,a+1+n,cmp);	
	for (int i=1;i<=n;i++) rk[a[i].d]=i;//离散化
	
	for (int i=n;i>=1;i--)
	{
		_update(rk[i],1);
		cnt+=_sum(rk[i]-1);
	}
	printf("%lld",cnt);
	return 0;
}

C.严格上升子序列数

需要小小地dp一下

设状态 \(f_{i,a_{j}}\) 表示长度为 \(i\) ,以 \(a_{j}\) 结尾的严格上升子序列的个数。这样显然,状态转移方程为

\[f_{i,a_{j}}=\sum _{k=1,a_{k}<a_{j}} ^{j-1}{f_{i-1,k}} \]

这里可以用类似于树状数组维护逆序对的方法维护\(\sum _{k=1,a_{k}<a_{j}} ^{j-1}{f_{i-1,k}}\)

再次注意到较大的值域,所以还要先离散化

#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1010;
const int MOD=1e9+7;
int T;
int n,m;
struct node{
	int x,d;
}a[N];
int rd[N];
int t[N];
int f[N][N];
int cnt;

bool cmp (node x,node y)
{
	if (x.x==y.x) return x.d<y.d;
	return x.x<y.x;
}
int lowbit(int x) { return x&-x; }
void _update(int d,int x)
{
	while (d<=N)
	{
		t[d]+=x;
		d+=lowbit(d);
	}
}
int _sum(int x)
{
	int res=0;
	while (x)
	{
		res+=t[x];
		x-=lowbit(x);
	}
	return res;
}
signed main()
{
	scanf("%lld",&T);
	for (int k=1;k<=T;k++)
	{
		cnt=0;
		memset(f,0,sizeof f);
		scanf("%lld%lld",&n,&m);
		for (int i=1;i<=n;i++) 
		{
			scanf("%lld",&a[i].x);
			a[i].d=i;
		}
		
		sort(a+1,a+1+n,cmp);
		for (int i=1;i<=n;i++) rd[a[i].d]=i;//离散化
		for (int i=1;i<=n;i++) f[1][i]=1;//边界
		rd[0]=1;//还是边界
		
		for (int i=2;i<=m;i++)
		{
			memset(t,0,sizeof t);
			for (int j=1;j<=n;j++)
			{
				f[i][j]=(f[i][j]+_sum(rd[j]-1))%MOD;
				_update(rd[j],f[i-1][j]);
			}
		}
		
		for (int i=1;i<=n;i++) cnt=(cnt+f[m][i])%MOD;
		printf("Case #%lld: %lld\n",k,cnt);
	}
	return 0;
}

G.星星问题

这题的输入非常的友好,已经是有序了。那么直接二维数点就行了。(或者叫,超级简化版的二维数点?)

每输入一个点就按照 \(x\) 坐标查询一下,然后再把 \(x\) 放进去即可。因为给出的 \(y\) 有序,就不用考虑 \(y\)(懂事的输入没糖吃)

#incIude <bits/stdc++.h>
using namespace std;
const int N=15005;
const int M=32005;
int n;
int tr[M];
int ans[N];

int lowbit(int x) {  return x&-x;  }
inline void add(int d,int x)
{
	while (d<=M) tr[d]+=x,d+=lowbit(d);
}
inline int sum(int x)
{
	int res=0;
	while (x)
	{
		res+=tr[x];
		x-=lowbit(x);
	}
	return res;
}
int main()
{
	scanf("%d",&n);
	for (int i=1,x,y;i<=n;i++) 
	{
		scanf("%d%d",&x,&y);
		x++;
		ans[sum(x)]++;
		add(x,1);	
	}
	
	for (int i=0;i<=n-1;i++) printf("%d\n",ans[i]);
	return 0;
}

H.维护差数

在此批判YbtOj,并没有说 \(i<j\) 的条件(我很恼火因为我刚开始排序了)

给式子进行一个简单的移项,就变成了满足 \(a_{i}-i<a_{j}-j\ ,\ i<j\) 的数会产生贡献。于是套个板子就出来了。

注意\(a_{i}-i\)可能会小于\(0\),所以还要加上一个偏移量。

#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
const int MOD=12345;
int n;
int a[N];
int tr[N<<1]; 
int ans;

int lowbit(int x) {  return x&-x;  }
void add(int d,int x)
{
	while (d<(N<<1)) tr[d]+=x,d+=lowbit(d);
}
int sum(int x)
{
	int res=0;
	while (x) 
	{
		res+=tr[x];
		x-=lowbit(x);
	}
	return res;
}
signed main()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		a[i]=a[i]-i+N;
	}
	
	for (int i=1;i<=n;i++)
	{
		add(a[i],1);	
		ans=(ans+sum(a[i]-1))%MOD;
	} 
	printf("%lld",ans);
	return 0;
}

posted @ 2024-11-28 19:42  还是沄沄沄  阅读(6)  评论(0编辑  收藏  举报