题目链接
https://www.luogu.org/problemnew/show/P4198
分析
一句话题意,一条数轴上有若干楼房,坐标为\(xi\)的楼房有高度\(hi\),那么它的斜率为\(hi/xi\),操作包含单元素高度修改,动态询问最长上升斜率序列个数
一开始想什么分治或是离线操作之类的,却因为水平低并不会做,看了题解居然发现就是线段树!看了一下感觉真妙啊,线段树真是个神奇的数据结构
线段树维护两个东西\(sum[now],mx[now]\);
\(sum[now]\)是\(now\)管辖区间\([l,r]\)的最长上升斜率个数
类似的\(mx[now]\)就是那个区间最大斜率,也就是最长上升序列中最右边的
我们假设更新操作递归到\([L,R]\)区间,设\(p\)为\([L,mid]\)区间最大斜率
\(mx[now<<1]\)
那么\([L,R]\)区间的贡献\((sum[now])\)显然等于\([L,mid]\)区间贡献\((sum[now<<1])\) \(+\) 统计\([mid+1,R]\)区间中大于\(p\)的最长上升序列个数.
那么怎么计算\([mid+1,R]\)区间中的大于\(p\)的最长上升序列个数呢,这个我在代码中给出了详细的注释
代码
#include <cstdio> #include <cstdlib> #include <algorithm> #include <cctype> #include <cstring> #include <cmath> #define ll long long #define ri register int using std::min; using std::max; template <class T>inline void read(T &x){ x=0;int ne=0;char c; while(!isdigit(c=getchar()))ne=c=='-'; x=c-48; while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-48; x=ne?-x:x;return ; } const int maxn=100005; const int inf=0x7fffffff; int n,m,t; double dta,p; struct Segment_Tree{ int sum[maxn<<2];//指now管辖的区间内最长上升序列个数 double mx[maxn<<2];//指now管辖区间内最大斜率 int calc(int now,int l,int r){ if(l==r) return mx[now]>p; int mid=(l+r)>>1; if(mx[now<<1]<=p)return calc(now<<1|1,mid+1,r);//如果左区间的最大值小于p,那么左区间贡献为0,计算右区间贡献 return sum[now]-sum[now<<1]+calc(now<<1,l,mid); //如果左区间有贡献,那么右边的不用管,因为sum[now]中本来就是递归到这里时[l,r]区间内最长上升序列个数 //如果左区间已有了贡献,说明左区间存在大于p的值,那么右区间计入sum[now]中的斜率肯定也能对现在答案产生贡献 //但是左区间中有些对sum[now]产生贡献的斜率此时可能小于p,因此答案就是右区间贡献+递归处理左区间贡献 //右区间贡献就是sum[now]-sum[now<<1] } void update(int now,int l,int r){ if(l==r){ mx[now]=dta; sum[now]=1; return ; } int mid=(l+r)>>1; if(t<=mid)update(now<<1,l,mid); else update(now<<1|1,mid+1,r); mx[now]=max(mx[now<<1],mx[now<<1|1]); p=mx[now<<1];//左区间最大值 sum[now]=sum[now<<1]+calc(now<<1|1,mid+1,r);//计算右区间贡献 return ; } }T; int main(){ int x; read(n),read(m); while(m--){ read(t),read(x); dta=(double)x/t; T.update(1,1,n); printf("%d\n",T.sum[1]); } return 0; }
来源:https://www.cnblogs.com/Rye-Catcher/p/9560934.html