树状数组学习笔记

前言

模板是很久以前过的,每逢大考都重新打一遍,但始终用不上(因为只会抄板子。。。)

这几天刷了一些树状数组的题,有了一些想法

概念

一个很不严谨的idea:树状数组是一个可以快速简洁地维护会变化的序列的前缀和的数据结构

其主要用途:

  1. 逆序对
  2. 查询插入某数时刻有多少数大于(小于)某个数,常与离散化配合食用
  3. 找一段区间有多少不同的数(或出现n次以上的数)
    ......

例题

  1. P2163 [SHOI2007]园丁的烦恼

这是一道二维数点的经典模板题

思路:

1. 首先考虑二维前缀和,每个矩型分为4个矩形求答案
2. 把询问点(每个矩形右上角的顶点)和真实存在的点按以x坐标为第一关键字,y坐标为第二关键字排序,坐标相同时询问点在后,这时查询该点时只存在这个点左边和正下方的点,于是我们只用在这些点里找出纵坐标小于该点的点个数,用树状数组维护即可

code

#include<bits/stdc++.h>
#define int long long
#define N 2500010
#define re register 
using namespace std;
int n,m,ty[N],cnt,t[N],ans[N];
template <class T> inline void read(T &x)
{
	x=0;int g=1;char s=getchar();
	for (;s<'0'||s>'9';s=getchar()) if (s=='-') g=-1;
	for (;s>='0'&&s<='9';s=getchar()) x=(x<<1)+(x<<3)+(s^48);
	x*=g;
}
struct node
{
	int x,y,id,t; 
}e[N];
bool cmp(node x,node y)
{
	if (x.x==y.x) 
	{
		if (x.y==y.y) return x.t<y.t;
		return x.y<y.y;
	}
	return x.x<y.x;
}
void insert(int id,int x,int y,int t)
{
	++cnt;e[cnt].x=x;e[cnt].y=y;e[cnt].t=t;e[cnt].id=id;ty[cnt]=y;
}
void add(int x,int y)
{
	for (;x<=cnt;x+=(x&(-x))) t[x]+=y;
} 
int ask(int x)
{
	int tmp=0;
	for (;x;x-=(x&(-x))) tmp+=t[x];
	return tmp;
}
signed main()
{
	re int i,j,x,y,z,op,a,b,c,d;
	read(n);read(m);
	for (i=1;i<=n;i++) 
	{
		read(x);read(y);
		insert(i,x,y,0);
	}
	for (i=1;i<=m;i++)
	{
		read(a);read(b);read(c);read(d);
		insert(i,a-1,b-1,1);
		insert(i+m,a-1,d,1);
		insert(i+2*m,c,b-1,1);
		insert(i+3*m,c,d,1);
	}
	sort(e+1,e+cnt+1,cmp);
	sort(ty+1,ty+cnt+1);
	for (i=1;i<=cnt;i++)
	{
		int tmp=lower_bound(ty+1,ty+cnt+1,e[i].y)-ty;
		if (e[i].t==0) add(tmp,1);
		ans[e[i].id]+=ask(tmp);
	} 
	for (i=1;i<=m;i++)
	{
		int tmp=ans[i]+ans[i+3*m]-ans[i+m]-ans[i+2*m];
		printf("%lld\n",tmp);
	}
	return 0;
}
  1. P1966 [NOIP2013 提高组] 火柴排队

做这题的时候,我偷偷看了一眼题解是往逆序对的方向去想

看一下条件,是它最小就是相当于把A,B数组分别排序,每个对应的A[i],B[i]必须是对应的,但每一对之间的相对位置不做要求

这时要求最小次数

显然A,B数组分别排序在绝大部分情况是多了很多步的

联想一下逆序对,逆序对的一个含义是把一个无序的序列通过交换变成有序的最小次数

所以我们想把a数组看成一个“有序”的数组,把B数组往A数组那样靠,不也是最小的吗?(把A数组往B数组那样靠也是一样的道理)

比如

A 1 3 4 2

B 1 4 2 3

一个数组对应一个字母(便于区分)

1->a,3->b,4->c,2->a

A a b c d(A 1 2 3 4)

B a c d b(B 1 3 4 2)

这时候A数组就是有序的啦

所以答案就是B数组的逆序对数

#include<bits/stdc++.h>
#define N 100010
#define int long long 
#define inf 0x7f7f7f7f
using namespace std;
int n,ans,a[N],b[N],c[N],d[N],num[N],rank[N],t[N]; 
int mod=100000000-3;
template <class T> inline void read(T &x)
{
	x=0;int g=1;char s=getchar();
	for (;s>'9'||s<'0';s=getchar()) if (s=='-') g=-1;
	for (;s<='9'&&s>='0';s=getchar()) x=(x<<1)+(x<<3)+(s^48);
	x*=g;
}
int lowbit(int x)
{
	return x&-x;
}
void add(int x,int y)
{
	for (;x<=n;x+=lowbit(x)) t[x]+=y;
}
int ask(int x)
{
	int tmp=0;
	for (;x;x-=lowbit(x))	tmp=tmp+t[x];
	return tmp;	
}
signed main()
{
	int i,j,x,y,z,op;
	read(n);
	for (i=1;i<=n;i++) read(a[i]),c[i]=a[i];
	for (i=1;i<=n;i++) read(b[i]),d[i]=b[i];
	sort(c+1,c+n+1);sort(d+1,d+n+1);
	for (i=1;i<=n;i++)
	{
		a[i]=lower_bound(c+1,c+n+1,a[i])-c;
		b[i]=lower_bound(d+1,d+n+1,b[i])-d;
	}
	for (i=1;i<=n;i++)	num[a[i]]=i;
	for (i=1;i<=n;i++)	rank[i]=num[b[i]];
	for (i=1;i<=n;i++)	add(rank[i],1),ans=(ans+i-ask(rank[i]))%mod;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-05-07 22:42  Ritalc  阅读(60)  评论(0编辑  收藏  举报