线段树(进阶)

2. 主席树

2.1 【模板】可持久化线段树 1(可持久化数组)

维护一个可持久化数组。

  1. 从某一个历史版本修改某个数的值
  2. 求某个版本的某处值。

每次都会进行版本更新(第二个操作生成一个完全一样的版本)

2.2 主席树

主席树的全称:可持久化线段树。

版本相当于一个树:

如果没有版本问题,完全可以直接用数组,但这里我们还是使用线段树:

接下来我们为 a1a1+m,a3a3+va_1\gets a_1+m,a_3\gets a_3+va7a7+wa_7\gets a_7+w

很显然,未改变的节点约为 2nlogn2n-\log n 个,而且 logn\log n 为链。如果直接再建一棵线段树,在空间上是很亏的。所以考虑直接这么建:

尽管图很抽象,但的确很抽象。

解释一下:由于每一次分叉会差一个节点,那么新建的线段树应该让所有节点(除了叶子)连上其他未接上的点。

很显然,每一次增加 logn\log n 个点,总用点不超过 n+qlognn+q\log n,不好爆。

观察主席树,很显然每个节点不止一个直接祖先,祖先也不止一个。

通过主席树,如果想查询,直接找版本就可以了。

可问题是:怎么存储和加边呢?

线段树使用左儿子 2x2x,右儿子 2x+12x+1 的办法来存储的。

而主席树用的是结构体:

struct tree{
	int l,r,val;
	//左儿子,右儿子,权值
}

那么加边就很简单了,指一下就可以了。

如何查询呢?我们可以用 qq 个数组来存历史版本。

然后,就没有然后了。

封装主席树:

struct ZX__tree{
	struct tree{
		int l,r,val;
	};
	tree a[32000006];
	int tr[32000006];
	int top;
	int build(int node,int l=1,int r=n)
	{
		node=++top;
		if(l==r)
		{
			a[node].val=read();
			return node;
		}
		int mid=(l+r)/2;
		a[node].l=build(a[node].l,l,mid);
		a[node].r=build(a[node].r,mid+1,r);
		return node;
	}
	int add(int &node)
	{
		top++;
		a[top]=a[node];
		return top;
	}
	int update(int w,int val,int node,int l=1,int r=n)
	{
		node=add(node);
		if(l==r) a[node].val=val;
		else
		{
			int mid=(l+r)/2;
			if(w<=mid) a[node].l=update(w,val,a[node].l,l,mid);
			else a[node].r=update(w,val,a[node].r,mid+1,r);
		}
		return node;
	}
	int query(int w,int node,int l=1,int r=n)
	{
		if(l==r) return a[node].val;
		else
		{
			int mid=(l+r)/2;
			if(w<=mid) return query(w,a[node].l,l,mid);
			else return query(w,a[node].r,mid+1,r);
		}
	}
}a;

3. 扫描线

3.1 逆序对

水。

3.2 面积并

给定 nn 个矩形,求矩形并

我们可以用一个线从左往右(别问为什么图是从下往上)扫,统计对于每次扫到的区间面积。

我们可以对每个矩形左边和右边用 1100 标记,11 表示需要计算,00 表示不许计算。那么就能轻松维护他们的面积。

图和代码见 oi.wiki

3.3 统计矩形周长

我们可以从左往右,统计出红色线。同理,可以求出其他颜色的线

3.4 窗口的星星

3.5 总结

  • 相较于主席树而言,通过扫一维,能够用线段树等数据结构更加方便的维护另一维的信息
  • 但是通常要求离线

4. 其他

4.1 Hotel G

4.2 排序

4.3 poj2777/色板游戏

5. 题单

link

posted @ 2024-07-16 09:19  sLMxf  阅读(27)  评论(0)    收藏  举报  来源