[洛谷P4198]楼房重建(线段树维护单调栈)

题目

给一个序列,支持单点修改,每次修改后输出对这个序列做单调递增栈后栈的大小

思路

线段树维护单调栈模板题

先开个线段树,设(query(rt,l,r,x))表示询问([l,r])这段区间中比(x)大的数形成的单调栈大小,(mx[rt])表示该子树中的最大值
显然有(query(rt,l,r,x) = query(ls,l,mid,x) + query(rs,mid+1,r,mx[ls]))
每次都递归到底是不行的,考虑用一个中间值存其中一个询问,由于最终询问为(query(1,1,n,0)),设(ans[rt]=query(rt,l,r,0))
修改时mx显然可以直接(O(1))求的,而(ans)用上面的递推式,只需要快速得到(query(rs,mid+1,r,mx[ls]))即可

对于询问(query(rt,l,r,x))

  1. (mx[ls]leq x),即左区间没有贡献,直接递归右区间(query(rs,mid+1,r,x))

  2. (mx[ls]>x),左区间有贡献,此时递归左区间(query(ls,l,mid,x)),右区间为(query(rs,mid+1,r,mx[ls])=ans[rt]-ans[ls])

于是询问只需要递归(log)次,而修改的pushup共需要log次询问,总复杂度为(O(nlog^2n))

Code

#include<bits/stdc++.h>
#define N 100005
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
typedef long long ll;
int n,m,ans[N<<2];
double mx[N<<2];

template <class T>
void read(T &x)
{
	char c; int sign=1;
	while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
	while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
int query(int rt,int l,int r,double k)//查询大于k情况下单增的单调栈长度 
{
	if(l==r) return (mx[rt]>k);
	if(mx[rt]<=k) return 0;//全部都很小
	int mid=(l+r)>>1;
	if(mx[rt<<1]<=k) return query(rt<<1|1,mid+1,r,k);
	return query(rt<<1,l,mid,k)+ans[rt]-ans[rt<<1];
}
void modify(int rt,int l,int r,int x,double val)
{
	if(l==r) { ans[rt]=1; mx[rt]=val; return; }
	int mid=(l+r)>>1;
	if(x<=mid) modify(rt<<1,l,mid,x,val);
	else modify(rt<<1|1,mid+1,r,x,val);
	mx[rt]=Max(mx[rt<<1],mx[rt<<1|1]);
	ans[rt]=ans[rt<<1]+query(rt<<1|1,mid+1,r,mx[rt<<1]);
}
int main()
{
	read(n);read(m);
	while(m--)
	{
		int x,y;
		read(x);read(y);
		modify(1,1,n,x,1.0*y/x);
		printf("%d
",ans[1]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Chtholly/p/11793219.html