ACM暑期集训4

今天主要学习了线段树,树状数组,ST表,差分,分块和树剖(好吧,这个已经没听懂了)

1.线段树

线段树涉及许多应用和思想,以下是今天所学

线段树主要用于处理一段连续区间的插入,查找,统计,查询等操作。

复杂度:设区间长度是n,所有操作的复杂度是logn级别。

性质:
           线段树是平衡的2叉树,最大深度logn(n为线段树所表示区间的长度)

树中的每一个节点代表对应一个区间(叶子节点是一个点……)

每个节点(所代表的区间)完全包含它的所有子孙节点 对于任意两个节点(所代表的区间):要么完全包含,要么互不相交。

1)单点修改,区间查询

先来道裸题

有个长度为n的序列a[],m(m<=100000)次询问, ·

有两种操作: ·  

1.询问: 询问区间L到R的数的和 ·

2.修改: 将序列中下标为p的的元素的值加v ·

要求输出每一次询问的结果…… (n<=100000,m<=100000)

直接上模板代码(解释见注解)

struct seg{
	int l,r,sum;
}tree[2*n];

void build(int p,int l,int r) //建树,从头结点(1,l,r)开始
{
	tree[p].l=l;
	tree[p].r=r;
	tree[p].sum=0;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}

void add(int p,int x,int y) //单点修改,将x出的点加y,可将读入视为修改,从(1,i,y)开始
{
	if(tree[p].l==tree[p].r)
	{
		tree[p].sum+=y;
		return;
	}
	int mid=(tree[p].l+tree[p].r)>>1;
	if(x<=mid) add(p<<1,x,y);
	else add(p<<1|1,x,y);
	tree[p].sum=tree[p<<1].sum+tree[p<<1|1].sum;
}

int ask(int p,int l,int r) //询问区间(l,r)元素的和
{
	if(l==tree[p].l&& r==tree[p].r) return tree[p].sum;
	int mid=(tree[p].l+tree[p].r)>>1;
	if(r<=mid) return ask(p<<1,l,r);
	else if (l>mid) return ask(p<<1|1,l,r);
	else return ask(p<<1,l,mid)+ask(p<<1|1,mid+1,r);
}

2)区间修改,区间求和,lazy思想

老样子,来道裸题

有个长度为n的序列a[],m次询问

请实现两种操作:

1. 询问[L,R]的区间和

2.修改:将[L,R]的元素全部加x   n<=100000,m<=100000

先来介绍lazy思想:

记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。

在此通俗的解释Lazy的意思,比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,它的节点标记为rt, 这时我们可以一步更新此时rt节点的sum[rt]的值,sum[rt] += c* (r - l + 1),更这个节点的Lazy标记。注意关键的时刻来了,如果此时按照常规的线段树的update操作,这时候还应该更新rt子节点的sum[]值,而Lazy思想恰恰是暂时不更新rt子节点的sum[]值,到此就return,直到下次需要用到rt子节点的值的时候才去更新,这样避免许多可能无用的操作,从而节省时间。

#define L o<<1
#define R o<<1|1
#define LL long long
#define MAX 100000

struct node
{
	int l,r;
	LL sum;
	LL lazy;
	int length;		//延迟标记,区间长度
}Tree[MAX<<2];

void push_up(int o)
{
	Tree[o].sum = Tree[L].sum + Tree[R].sum;
}

void build(int o,int l,int r)
{
	Tree[o].l = l;
	Tree[o].r = r;
	Tree[o].lazy = 0;
	Tree[o].length = r - l + 1;
	if (l == r)
	{
		scanf ("%lld",&Tree[o].sum);
		return;
	}
	int mid = (l + r) >> 1;
	build(L,l,mid);
	build(R,mid+1,r);
	push_up(o);
}

void push_down(int o)
{
	if (Tree[o].lazy)
	{
		Tree[L].sum = Tree[L].sum + Tree[o].lazy * Tree[L].length;
		Tree[R].sum = Tree[R].sum + Tree[o].lazy * Tree[R].length;
		Tree[L].lazy += Tree[o].lazy;
		Tree[R].lazy += Tree[o].lazy;
		Tree[o].lazy = 0;
	}
}

void add(int o,int l,int r,int lazy)
{
	if (Tree[o].l == l && Tree[o].r == r)
	{
		Tree[o].sum += Tree[o].length * lazy;
		Tree[o].lazy += lazy;
		return;
	}
	push_down(o);
	int mid = (Tree[o].l + Tree[o].r) >> 1;
	if (mid >= r)
		add(L,l,r,lazy);
	else if (l > mid)
		add(R,l,r,lazy);
	else
	{
		add(L,l,mid,lazy);
		add(R,mid+1,r,lazy);
	}
	push_up(o);
}

LL ask(int o,int l,int r)
{
	if (Tree[o].l == l && Tree[o].r == r)
		return Tree[o].sum;
	push_down(o);
	int mid = (Tree[o].l + Tree[o].r) >> 1;
	if (mid >= r)
		return ask(L,l,r);
	else if (l > mid)
		return ask(R,l,r);
	else
	{
		return (ask(L,l,mid) + ask(R,mid+1,r));
	}
}

对于其他的单点/区间修改,区间最值其实主要对push_down和其他小细节修改即可

3)合并思想

来道裸题

单点修改,查询区间最大连续子段和 (n<=100000,m<=100000)

分析:

定义lm为区间的左连续区间的最大和,rm为区间的右连续区间的最大和

mx为区间的最大子段和,sum为区间和

对于一个区间[L,R] mx[L,R]=max(max(mx[L,mid],mx[mid+1]),rm[L,mid]+lm[mid+1,R]])

那么只需维护这三个量即可。

lm[L,R]=max(lm[L,mid],sum[L,mid]+lm[mid+1,R])

rm[L,R]=max(rm[mid+1,R],rm[L,mid]+sum[mid+1,R])

单点修改很容易解决了 查询时,需要返回三元组(mx,lm,rm),即可维护答案。

2.树状数组

主要解决动态区间,前缀和的问题,即可以实现单点修改,询问前缀和

利用树状数组+差分,也可以解决单点修改询问区间和,区间加法询问单点值,甚至区间加区间求和也可以做。详细应用见传送门 

常数小,代码短,二维也很容易实现

还有一些其他问题也可以通过树状数组来解决,比如区间。所以一般用线段树就够了,树状数组能做的,线段树一定能做,很少有出题人卡这个(所以还是有的),但是又短又小又快。。。能用为啥不用

树状数组可以很容易的解决最长上升子序列,还有一些高维偏序问题一般会套用树状数组,应用还有很多。

int bit[1000],n;
void add(int x,int val) //a[i]+=val 单点修改
{
     for(; x < n ; x += x&-x ) bit[i] += val;
}

int ask(int x) //sum[1~x]
{
     int sum = 0;
     for(; x > 0; x -= x&-x) sum += bit[x];
     return sum;
}

3.ST表

主要解决静态区间,最值问题,即没有修改,只有询问区间最值

容易推广到二维

倍增的思想很重要!

线段树预处理是o(nlogn),单次查询o(logn),空间o(n)

ST表预处理是o(nlogn),单次查询o(1),空间o(nlogn)

int log[maxn],f[17][maxn];
int ask(int x,int y)
{
    int k = log[y-x+1];
    return max(f[k][x],f[k][y-(1<<k)+1]);
}

void init()
{
    for(int i = 2; i <= n; ++i) log[i] = log[i>>1] + 1;
    for(int i = 1; i <= n; ++i) f[0][i] = a[i];
    for(int j = 1; j <= log[n]; ++j) 
          for(int i = 1; i+(1<<j-1) <= n; ++i) 
                f[j][i] = max(f[j-1][i],f[j-1][i+(1<<j-1)]);
}

4.分块(上课听到这里已经......只能搬PPT了)

int n,b,num l[maxn*maxn],r[maxn*maxn],belong[maxn];
void init()
{
    b=sqrt(n);
    num = n/b;
    if(n%b) ++num;
    for(int i = 1;i <= n; ++i) belong[i] = (i-1)/b+1;
    for(int i = 1;i <= num; ++i)
         l[i] = (i-1)*b+1,r[i] = i*b;
    r[num] = n;
    //+块内维护的各种东西
}

5.后续知识点(现在先了解吧)

线段树相关:可持久化线段树,线段树套bulabula,和其他各种东西结合,zkw线段树

其他常用数据结构:trie,树剖,splay,Hash表,左偏树,莫队,LCT,K-D tree,Treap,可持久化xxx...

c++的pb_ds库:封装了很多数据结构,比如:平衡树,哈希表,字典树,堆...

猜你喜欢

转载自blog.csdn.net/qq_41383801/article/details/81192333
今日推荐