[洛谷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\n",ans[1]);
	}
	return 0;
}
posted @ 2019-11-04 17:28  擅长平地摔的艾拉酱  阅读(142)  评论(0编辑  收藏  举报
/*取消选中*/