BZOJ2957楼房重建
传送门 https://www.luogu.org/problemnew/show/P4198
蒟蒻渣渣禹看到这个题时十分不想写线段树emmmm
于是乎。。。。分块!!!
分块。思路就是:如果一个楼房能被看见,当且仅当它的房顶到(0,0)连线的斜率 比前面的都大。我们暴力维护每个块内斜率的最长上升序列,然后查询的时候二分找到块内第一个能看到的,那么它之后的所有最长上升序列里的元素就都能被看到,再去搜下一个块就好了。
下面我来讲下具体过程,我们要求的个数,就是斜率最长上升子序列,大家思考一下。。。显然成立。
我们设a,b以及他们的高度为h[a],h[b],如果a不会被b挡住,那么我们可以推出这样一个式子:h[a]/a >= h[b]/b,交叉相乘之后可得:h[a]b >= h[b]a。
对于原点的话我们只要判断h[a]是否大于0就行了。
修改:然后我们暴力更新块内元素信息,每次清零,重新维护块内最长上升序列长度。
查询:我们设last为上一个满足条件的块的末尾节点,那么从第一块开始到最后一块,在块内进行二分查找出与last相比刚满足条件的点,那么,从这个点一直到末尾均为合法序列,那么ans=这段序列的长度qwq,然后更新last为本块的末尾。
我们在暴力搞定每个块的递增数列,把这些斜率从小到大塞到一个栈里边(时间复杂度O(n/b),b为块的大小)。
然后查找时从头开始,在每个块对应的栈中二分查找第一个斜率比前一个大的位置,这个位置和栈里面后边的位置都能被看到(时间复杂度O(blog(n/b)))。
总时间复杂度为O(n(n/b + blog(n/b)))≈O(n(n/b + blogn))。
这样一来b=√(n/logn)/2有奇效。想知道为什么么?
通过我巨水无比的计算,以及一次次试错试出的,不然会tle,除非你开o2,强烈建议用快读。
不用o2优化代码如下qwq
#include<cmath> #include<cstdio> #include<cstring> #include<algorithm> #define N 100005 #define LL long long using namespace std;; int n,m,b,h[N],p[N]; struct stack { int s[N],top; }t[330]; bool check(LL a,LL b) { if(!b) return h[a] > 0; return (LL)(h[a] * b) > (LL)(h[b] * a); } inline int read() { int w=0; char ch; while(ch<'0'||ch>'9') { ch=getchar(); } while(ch>='0'&&ch<='9') { w=(w<<3)+(w<<1)+ch-'0'; ch=getchar(); } return w; } int main() { n=read(); m=read(); b = sqrt(n * log(n) / 2); for(register int i = 1;i <= n;i++) p[i] = (i - 1) / b + 1; while(m--) { int x,ans = 0; x=read(); h[x]=read(); register int l = (p[x] - 1) * b + 1,r = min(n,p[x] * b); t[p[x]].top = 0; for(register int i = l;i <= r;i++) if(check(i,t[p[x]].s[t[p[x]].top])) t[p[x]].s[++t[p[x]].top] = i; for(register int i = 1,last = 0;i <= p[n];i++) { register int q = 0; l = 1,r = t[i].top; while(l <= r) { register int mid = (l + r) >> 1; if(check(t[i].s[mid],last)){ q = mid; r = mid - 1; } else l = mid + 1; } if(q){ last = t[i].s[t[i].top]; ans += t[i].top - q + 1; } } printf("%d\n",ans); } }