2957: 楼房重建

题目链接

题目大意:数轴上有n座楼,初始高度为0,每次可以改变某栋楼的高度,求每次改变高度之后从原点可以看到几栋楼

题解:记每栋楼楼顶与原点连线的斜率,那么一栋楼可见当且仅当前面所有楼的斜率都小于这栋楼
分块:块内维护特殊的lis,4 1 2 3 5 那么维护的序列就是4 5
块内暴力修改,查询的时候从下一块找到比这一块max大的即可

线段树做法:结点维护ans和mx,ans表示这个区间的答案,mx表示这个区间的最大斜率。
合并区间:对于节点x,ls的答案可以完全加入x的答案,接下来考虑rs
记ls最大值为 M
若rs最大值小于等于M,则rs没有贡献
若大于 M ,把rs分成左右区间处理
若rs左区间mx小于等于M,则递归处理rs右区间
若rs左区间mx大于 M <script id="MathJax-Element-221" type="math/tex">M</script>,则递归处理rs左区间,同时加上rs右区间的答案
右子区间的答案要用右区间答案-左子区间答案,不能直接调用右子区间本身答案,因为其本身答案没有考虑左子区间的约束。

我的收获:强强强

分块

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

#define M 100100  
#define eps 1e-10  

int n,m,blo,num,pos[M];  
double a[M];  

struct Block{
    int tot,l,r;
    double ele[1010];
    void rebuild()//本来TLE了,换成结构体函数就过了…… 
    {
        double tmp=0;tot=0;
        for(int i=l;i<=r;i++)
            if(a[i]>tmp+eps)
                tmp=a[i],ele[++tot]=a[i];
    }
    int cal(double &x)
    {
        int ret=(tot+1)-(upper_bound(ele+1,ele+tot+1,x+eps)-ele);
        if(ret) x=ele[tot];
        return ret;
    }
}b[120];

void work()
{
    int x,y;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        a[x]=(double)y/x;
        b[pos[x]].rebuild();
        int ans=0;double tmp=0;
        for(int j=1;j<=num;j++)  
            ans+=b[j].cal(tmp);
        printf("%d\n",ans);
    }
}

void init()
{
    cin>>n>>m;
    blo=sqrt(n*log(n)/log(2)/2);num=n/blo+1;
    for(int i=1;i<=n;i++) pos[i]=(i-1)/blo+1;
    for(int i=1;i<=num;i++)
        b[i].l=(i-1)*blo+1,b[i].r=min(i*blo,n);
}

int main()
{
    init();
    work();
    return 0;
}

线段树

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

#define M 100005
#define ls x<<1
#define rs x<<1|1
#define lson l,m,x<<1
#define rson m+1,r,x<<1|1
#define root 1,n,1

int n,m,ans[M<<2],tl[M<<2],tr[M<<2];
double mx[M<<2];

int cal(double k,int x)
{
    int l=tl[x],r=tr[x];
    if(l==r) return mx[x]>k;//特判叶子结点
    if(mx[ls]<=k) return cal(k,rs);
    return ans[x]-ans[ls]+cal(k,ls);
}

void pushup(int x){mx[x]=max(mx[ls],mx[rs]);ans[x]=ans[ls]+cal(mx[ls],rs);}

void build(int l,int r,int x)
{
    if(l==r) return ;
    int m=(l+r)>>1;
    tl[x]=l,tr[x]=r;
    build(lson);build(rson);
}

void updata(int p,double k,int x)
{
    int l=tl[x],r=tr[x];
    if(l==r){ans[x]=1;mx[x]=k;return ;}
    int m=(l+r)>>1;
    if(p<=m) updata(p,k,ls);
    else updata(p,k,rs);
    pushup(x);
}

void work()
{
    int x,y;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        updata(x,(double)y/x,1);
        printf("%d\n",ans[1]); 
    }
}

void init()
{
    cin>>n>>m;
    build(root);
}

int main()
{
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值