【BZOJ2957】楼房重建(线段树)
大致题意: 有\(n\)个楼房,第\(i\)个楼房可以表示为连结\((i,0)\)和\((i,H_i)\)的线段,且第\(i\)个楼房可以被看见当且仅当连结\((0,0)\)和\((i,H_i)\)的线段与表示其他楼房的线段无交点。每次单点修改\(H_i\),求每次修改后能看到几个楼房。
前言
\(Jan\ 29th\)刷题计划(6/6),算法标签:线段树。
终于完成了今天的计划了!
斜率
令楼房\(_i\)表示连结\((i,0)\)和\((i,H_i)\)的线段,视线\(_i\)表示连结\((0,0)\)和\((i,H_i)\)的线段。
考虑视线\(_i\)与楼房\(_{1\sim i-1}\)无交点,就是视线\(_i\)的斜率大于视线\(_{1\sim i-1}\)的斜率。而此处的斜率,即为\(\frac {H_i}i\)。
也就是说,题目要求的就是视线斜率的上升子序列的长度(注意,不是最长上升子序列)。
线段树
考虑用线段树进行维护。
对于线段树上每一个点,我们维护两个信息:这个点所代表的区间的最大值\(Mx\)和上升子序列的长度\(f\)。则显然每次的答案就是根节点的\(f\)。
\(Mx\)的维护就是常规操作,而\(f\)的维护就比较有技巧了。
我们可以新定义一个函数\(Calc(x,l,r,rt)\),代表在\([l,r]\)这个区间里大于\(x\)的上升序列长度,则:
\[f(rt)=f(lc)+Calc(Mx(lc),mid+1,r,rc)
\]
\[Calc(x,l,r,rt)=\begin{cases}f(rt)&l=r且x<Mx(rt)\\0&l=r且x\ge Mx(rt)\\Calc(x,l,mid,lc)+f(rt)-f(lc)&l≠r且x<Mx(lc)\\Calc(x,mid+1,r,rc)&l≠r且x\ge Mx(lc)\end{cases}
\]
这样我们就可以在\(O(logN)\)的时间复杂度内实现\(PushUp()\),因而总复杂度是\(O(Nlog^2N)\)的。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB double
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
int n,m;
class SegmentTree
{
private:
#define PT CI l=1,CI r=n,CI rt=1
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
#define PU(x) (O[x]=node(O[x<<1].f+Calc(O[x<<1].Mx,RT),max(O[x<<1].Mx,O[x<<1|1].Mx)))//注意上升子序列长度的上传
struct node {int f;DB Mx;I node(CI p=0,Con DB& t=0):f(p),Mx(t){}}O[N<<2];
public:
I void Upt(CI x,Con DB& y,PT)//单点修改
{
if(l==r) return (void)(O[rt]=node(1,y));int mid=l+r>>1;
x<=mid?Upt(x,y,LT):Upt(x,y,RT),PU(rt);
}
I int Calc(Con DB& x,PT)//求出区间内大于x的上升序列的长度
{
if(l==r) return x<O[rt].Mx?O[rt].f:0;int mid=l+r>>1;//特判边界
return x<O[rt<<1].Mx?Calc(x,LT)+O[rt].f-O[rt<<1].f:Calc(x,RT);//递归求解
}
I int Qry() {return O[1].f;}//整个序列的答案
}S;
int main()
{
RI i,x,y;for(scanf("%d%d",&n,&m),i=1;i<=m;++i)
scanf("%d%d",&x,&y),S.Upt(x,1.0*y/x),printf("%d\n",S.Qry());//线段树上存储斜率
return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒