bzoj 2957
一道用线段树维护单调栈的典型问题
首先不难把问题转化成一个斜率模型,设任意一栋楼坐标为$x_i$,高度为$y_i$,那么这栋楼的“斜率”即为$\frac{y_{i}}{x_{i}}$
那么不难发现,题目要求的是一个斜率上升的序列,而且必须从前向后依次选择
那么首先考虑暴力,不难发现,暴力每求一次是$O(n)$的(从前向后扫)
有没有更好的方法呢?
我们发现,这里的多组询问其实只是单点修改,那么能不能利用数据结构进行维护呢?
可以!
我们考虑线段树维护:对于每一个节点,维护两个量:$maxh$表示这个区间内的最大高度,$maxlen$表示这个区间内从左端点开始满足上述要求的最长子序列长度
那么答案显然就是根节点的$maxlen$
接下来考虑怎么维护这两个值
首先$maxh$的维护是线段树的基本操作,这里不再赘述
我们重点分析一下$maxlen$的维护
通过分析,可以得到下面几个性质:
首先,任何一段区间这样的序列都是从左端点开始的!
然后,在合并两个区间时,左区间是可以直接继承上去的!
这两个性质都比较显然,稍微提一下第二个:因为选取序列要求从左向右选取,因此在合并左右区间时左区间在大区间里仍然是开头的,不受到影响,而右区间则要受到左区间选取元素的影响,因此右区间需要重新计算
因此我们在合并左右区间时,只需要计算右区间的贡献即可
这也是本题的一个难点所在
那么重点在于分类讨论以及剪枝
首先,不难发现这样的合并过程一定要求右区间中最终选取的每个值都大于左区间的最大值,这样才能保证单调!
因此,首先我们需要判断:如果右区间最大值都不大于左区间最大值,直接返回0即可
然后,如果右区间原本选取的值都是合法的,那么我们可以直接合并,再考虑到右区间第一个值是一定被选的,因此我们只需要判断右区间的左端点(有点绕,想一想)是否大于左区间最大值,如果大于则直接继承右区间贡献即可
接下来,如果右区间中只剩了一个数,那么我们直接比较这个数与左边最大值大小即可
看完我的讨论,你知道我要干什么了吗?
递归求解!
如果右区间不满足上述三个条件,那么我们是需要递归处理的!
将右区间再分成左右两个区间,然后递归计算即可
等等,发没发现什么问题?
这样做的复杂度上界是$O(n)$的!因为最糟情况下可能退化成要递归整棵树求解!
因此我们还需要剪枝:
首先,我们判断一下如果左子区间的最大值都不大于左边最大值,那么我们直接递归求解右子区间即可
然后,如果左子区间最大值大于左边最大值,那么我们递归处理左侧,然后直接算出右侧即可
为什么可以直接算出右侧?
首先会忆一下问题:目前的所在区间是右区间,右区间的值是已经计算过的,我们的目的是要利用右区间计算出整个区间的答案
那么,由于右区间是已经计算过的,所以我们事实上已经知道右子区间(注意本文中区分“右区间”和“右子区间”,“右子区间”表示右区间的右侧子区间)中选取了几个数,他就等于右区间的值-左子区间的值(注意是原来算出的)!
因此我们递归处理左子区间,然后加上上述贡献即可
这样我们最多只走了一条树链,时间复杂度变成$O(logn)$级别
总时间复杂度$O(nlog^{2}n)$
代码:
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define rt1 rt<<1 #define rt2 (rt<<1)|1 using namespace std; int n,m; double k[100005]; struct Seg_tree { int maxlen; double maxh; }tree[400005]; int pushup(int rt,int l,int r,double lim) { if(tree[rt].maxh<=lim)return 0; if(k[l]>lim)return tree[rt].maxlen; if(l==r)return k[l]>lim; int mid=(l+r)>>1; if(tree[rt1].maxh<=lim)return pushup(rt2,mid+1,r,lim); return pushup(rt1,l,mid,lim)+tree[rt].maxlen-tree[rt1].maxlen; } void update(int rt,int l,int r,int posi) { if(l==r) { tree[rt].maxh=k[posi]; tree[rt].maxlen=1; return; } int mid=(l+r)>>1; if(posi<=mid)update(rt1,l,mid,posi); else update(rt2,mid+1,r,posi); tree[rt].maxh=max(tree[rt1].maxh,tree[rt2].maxh); tree[rt].maxlen=tree[rt1].maxlen+pushup(rt2,mid+1,r,tree[rt1].maxh); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); k[x]=(double)y/(double)x; update(1,1,n,x); printf("%d\n",tree[1].maxlen); } return 0; }