【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)与楼房(_{1sim i-1})无交点,就是视线(_i)的斜率大于视线(_{1sim 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)=egin{cases}f(rt)&l=r且x<Mx(rt)\0&l=r且xge Mx(rt)\Calc(x,l,mid,lc)+f(rt)-f(lc)&l≠r且x<Mx(lc)\Calc(x,mid+1,r,rc)&l≠r且xge 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
",S.Qry());//线段树上存储斜率
	return 0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ2957.html