仅维护当前区间影响类问题的线段树

Posted knife-rose

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了仅维护当前区间影响类问题的线段树相关的知识,希望对你有一定的参考价值。

兔队教我线段树/kel

在某处找到了这个,自己写个博客加深下印象,不过兔队的名字感觉和实际操作起来关系不大,于是自己口胡了一个名字。。

楼房重建

题意:有(n)栋楼,第(i)栋的高度为(h_i),在从((0,0))位置能看到多少栋楼

能看到当且仅当((0,0))((i,h_i))之间没有其他楼,且(h_i>0)

(h_i)支持单点修改

解法:

(s_i=frac{h_i}{i}),定义(s_0=0)

若一栋楼能被看到,则(s_i)(s)序列的严格前缀最大值

考虑用线段树维护两个值:区间(s_i)的最大值,仅考虑区间([l,r])影响下的答案

最大值很好维护,考虑下怎么合并两个区间的答案

我们发现左子树是没有影响的,答案可以直接继承过来,而右子树需要考虑左子树区间的影响

引入一个函数(calc)用来合并答案


ans[p]=ans[ls(p)]+calc(mid+1,r,rs(p),maxn[ls(p)]);

inline int calc(int l,int r,int p,int premax)
{
	if(l==r) return (maxn[p]>premax);
	if(maxn[ls(p)]>premax) return calc(l,mid,ls(p),premax)+(ans[p]-ans[ls(p)]);
	return calc(mid+1,r,rs(p),premax);
}

如果是叶子节点,那么这个节点信息大于前缀最大值就有贡献

如果左子树的最大值大于前缀最大值,那么说明左子树中还有可以统计的答案再加上总答案数减去左子树答案

注意不能直接用右子树答案,因子右子树答案没有算左子树区间的影响

如果左子树的最大值小于等于前缀最大值,那么左子树中应该是没有贡献的,直接进入右子树统计

那么合并的复杂是一个(log),总复杂度是(O(nlog^2n))

不过我们发现这个(calc)函数维护的信息需要满足区间可减性,如果是区间或区间最值就不行了

重新设置一下状态,我们维护的答案变为考虑([l,r])影响下的右子树的答案

ans[p]=calc(mid+1,r,rs(p),maxn[ls(p)]);

inline int calc(int l,int r,int p,int premax)
{
	if(l==r) return (maxn[p]>premax);
	if(maxn[ls(p)]>premax) return calc(l,mid,ls(p),premax)+ans[p];
	return calc(mid+1,r,rs(p),premax);
}

(calc)可以变为这样,就不用减法了

完整代码

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define ls(p) (p<<1)
#define rs(p) (p<<1|1)
#define mid ((l+r)>>1)
#define y1 qwq 
	inline int read()
	{
		int x=0;char ch,f=1;
		for(ch=getchar();(ch<‘0‘||ch>‘9‘)&&ch!=‘-‘;ch=getchar());
		if(ch==‘-‘) f=0,ch=getchar();
		while(ch>=‘0‘&&ch<=‘9‘){x=(x<<1)+(x<<3)+ch-‘0‘;ch=getchar();}
		return f?x:-x;
	}
	const int N=1e5+10,inf=1<<30;
	int n,m;
	int h[N];
	int ans[N<<2],sum[N<<2];
	inline bool check(int x,int y)
	{
		if(!y) return h[x];
		return h[x]*y>h[y]*x;
	}
	inline void build(int l,int r,int p)
	{
		ans[p]=l,sum[p]=0;
		if(l==r) return;
		build(l,mid,ls(p));build(mid+1,r,rs(p));
	}
	inline int calc(int l,int r,int p,int pre)
	{
		if(l==r) return check(l,pre);
		if(check(ans[ls(p)],pre)) return calc(l,mid,ls(p),pre)+sum[p];
		return calc(mid+1,r,rs(p),pre);
	}
	inline void update(int pos,int l,int r,int p)
	{
		if(l==r) return;
		if(pos<=mid) update(pos,l,mid,ls(p));
		else update(pos,mid+1,r,rs(p));
		ans[p]=check(ans[rs(p)],ans[ls(p)])?ans[rs(p)]:ans[ls(p)];
		sum[p]=calc(mid+1,r,rs(p),ans[ls(p)]);
	}
	inline void main()
	{
		n=read(),m=read();
		build(1,n,1);
		for(int x,y,i=1;i<=m;++i)
		{
			x=read(),y=read();
			h[x]=y;update(x,1,n,1);
			printf("%lld
",calc(1,n,1,0));
		}
	}
}
signed main()
{
	red::main();
	return 0;
}

以上是关于仅维护当前区间影响类问题的线段树的主要内容,如果未能解决你的问题,请参考以下文章

线段树 poj 2991

[AHOI2009]维护序列

数据结构线段树笔记2

线段树维护区间最大子段和

CF - 1108 E 枚举上界+线段树维护

bzoj4653: [Noi2016]区间