题解 P4198 【楼房重建】

Nemlit

2018-10-24 20:42:52

Solution

这道题思路真是妙啊QWQ ### [原题地址](https://www.luogu.org/problemnew/show/P4198) 题目大意:在一个平面内,求出所有的高度大于$0$的点与$(0,0)$的连线没有与之前的线段相交的楼房,带修改 看到带修改,显然会想到数据结构来维护,再看到再平面区间加减和区间查询,显然又会想到线段树。 那线段树的每一个节点要维护什么值呢? 看到题面自然会想到斜率 所以问题就可以转化成求一个最长递增的斜率 既然是单调递增,那么我们线段树的每一个节点可以维护这个节点所对应的区间的最大值,与从这段区间开头可以看到的区间内的所有楼房 那么我们要怎么查询呢? 答案显然是第一个节点(根节点)的答案 所以我们现在的主要问题就是怎么修改 对于每一个叶子节点,从这段区间头可以看到的楼房数量一定为$1$,区间斜率最大值一定为该点的斜率 那么合并呢? 显然合并区间的所有楼房一定可以看到左边的区间第一个位置看到的所有楼房(这应该很显然吧) 那么右区间呢? 我们可以先查找右区间的左区间的最大值,如果这个最大值比左区间的最大值小,那么有区间的左区间的所有答案一定看不到,所以我们就可以递归查找右区间的右区间 如果右区间的左区间的最大值比左区间的最大值大,那么原来被右区间的左区间挡住的现在一样会被挡住,我们就可以加上右区间的答案,所以我们可以递归查找右区间的左区间 但是这里有一个坑点,有区间的答案不一定是$ans$[右区间的右区间],因为右区间的答案可能被左区间挡住,所以有区间的答案一定是$ans$[右区间]-$ans$[右区间的左区间] 每次查询的复杂度为$O(1)$,我们每次只会递归一个儿子,所以每次查询右儿子答案的复杂度为$O(logn)$,故每次修改的复杂度为$O(log^2n)$ 其他的地方就和普通线段树一样了 上文可能有点~~(十分)~~抽象,可以结合代码理解 ``` #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #define re register #define il inline #define inf 123456789 #define debug printf("Now is %d\n",__LINE__); using namespace std; il int read() { re int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*f; } #define D double #define maxn 100005 int n,m,ans[maxn<<2]; D ma[maxn<<2]; #define ls k<<1 #define rs k<<1|1 #define max(a,b) a>b?a:b il int query(int k,int l,int r,D maxx) //maxx表是我们要查询的值,也就是我们要查询的比该值斜率大的斜率的楼房数 { if(ma[k]<=maxx) { return 0; }//如果这段区间的最大斜率比目前要查询的值大,那么显然对答案不会有影响,所以返回0 if(l==r) { return ma[k]>maxx; }//如果是叶子节点,那么如果该点的斜率大于要查询的值就返回1,否则返回0 else if(ma[ls]<=maxx) { return query(rs,((l+r)>>1)+1,r,maxx); }//如果左儿子的最大值小于等于查询值,就递归右儿子 return query(ls,l,(l+r)>>1,maxx)+ans[k]-ans[ls];//否则递归左儿子 } il void change(int k,int l,int r,int ll,D v) { if(l==ll&&r==ll) { ans[k]=1,ma[k]=v; return; } re int mid=(l+r)>>1; if(ll<=mid) change(ls,l,mid,ll,v); else change(rs,mid+1,r,ll,v); ma[k]=max(ma[ls],ma[rs]); //目前区间的最大值显然是左右区间的最大值的最大值 ans[k]=ans[ls]+query(rs,mid+1,r,ma[ls]); //该区间的答案是左区间的答案加上右区间的斜率大于左区间最大值的所有楼房 } int main() { n=read(),m=read(); for(re int i=1,l,r;i<=m;++i) { l=read(),r=read(); change(1,1,n,l,(D)r/l);//注意斜率要用double printf("%d\n",ans[1]); } return 0; } ```