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);
    }
}

  

转载于:https://www.cnblogs.com/excellent-zzy/p/10667424.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值