[洛谷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)\):
-
\(mx[ls]\leq x\),即左区间没有贡献,直接递归右区间\(query(rs,mid+1,r,x)\)
-
\(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;
}