【卬】【题】【线段树】NKOJ 3829 ZZ 的书

16 篇文章 0 订阅
2 篇文章 0 订阅

NKOJ 3829 ZZ 的书

问题描述
聪明伶俐、勤奋好学的ZZ 有许多书。

每天上课前,她将她的书一摞一摞地放在书桌上,共n 摞。这n 摞书摆成一列,每摞书
之间的间隔为1cm,第一摞书离桌面边缘的距离为1cm。每节下课后她便会从某一摞书中取出一些书,并将另一些书放入同一摞书中。

如果上一节课的内容很简单,ZZ 便会利用课间的时间在放完书后整理一下她的书,整理
时,ZZ 会选择从L 到R 的一段书,将它们的高度都调整为H。(调整前后书的高度和可能不等)

有一只蚂蚁十分敬佩ZZ 的勤奋刻苦,每次ZZ 拿完书后,它便站在书桌的边缘(靠近第
1 摞数的边缘),想数一数ZZ 到底有多少摞书,但它不可能走完整个书桌,于是它索性每次都待在边缘上的一个固定点,每次只数出它能看见的书。(说明:

这里写图片描述

已知ZZ每天有m节课,每次ZZ拿书后,蚂蚁按自己的方法都要数一次ZZ的书,但有时候它数不清楚,你需要帮助它数一下当时的书。

输入格式
第一行两个整数N、M。
第二行N个整数Hi,表示各摞数的高度,单位cm(Hi>0)。
接下来M行,每行先有三个整数Xi,Yi,D。表示该课间离边缘Xi cm的那一摞书经过ZZ的调整后,高度变为了Yi cm(1<=Xi,Yi<=n、Yi>0)。
如果D为1,表示ZZ整理了她的书,接下来会有3个整数L,R,H,表示ZZ将L到R的书高度调整为Hcm(1<=L,R<=n,H>0)。如果D为0,表示这个课间ZZ没有整理书。

输出格式
共m行,每行表示第m节课间,ZZ放完和整理完她的书后,蚂蚁数出的本数。

样例输入
输入样例1
3 6
2 1 4
2 5 0
1 2 0
3 6 0
2 1 0
1 1 0
1 2 1 2 3 5

输入样例2
4 4
2 2 3 2
2 4 0
3 7 0
4 1 1 3 4 6
4 8 0

样例输出
输出样例1
2
2
2
1
2
2

输出样例2
1
2
1
1

提示
数据范围
对于30%的数据,1<=n<=5000,1<=m<=8000
对于100%的数据,1<=n<=50000,1<=m<=100000

说明
放书是指改变某一摞的高度
整理是指改变某一段相邻数的高度为同一值

来源 YXV

题解:
1、花一秒看出对于每摞书应该只关心它的斜率(下文只比较斜率)
2、Lazy、区间内最大值、只看该段可以看到的书的数量(维护这个可使查询O(1))
3、维护“只看该段可以看到的书的数量”(以下简称num):
(1)、每个区间的num一定为左区间的num加上右区间受左区间影响后,仍能看到的数量
(2)、所以,需要设计一个cnt函数,返回在区间左侧最高为v时,该区间能被看到的数量:S.num=ls.num+cnt(rs,ls.max);
(3)、对于该区间:
若左儿子的max比v小,则问题转化为求右儿子的贡献
若左儿子的max比v大,则说明右儿子所做贡献不受影响,左儿子所做贡献需重新计算,返回 右儿子的贡献+左儿子重新计算的贡献

#include<cstdio> 
#include<iostream> 
using namespace std; 
const int need=100003; 

struct zz 
{ 
    int a,b,num,lazy; 
    double max; 
} w[need<<3]; 

int h[need]; 
//.................................................................... 
void putdown(int s)
{
    int t=w[s<<1].lazy=w[(s<<1)|1].lazy=w[s].lazy;
    w[s].lazy=0;
    w[s<<1].num=1,w[(s<<1)|1].num=1;
    w[s<<1].max=(double)t/w[s<<1].a,w[(s<<1)|1].max=(double)t/w[(s<<1)|1].a;
}
int cnt(int s,double v) 
{
    if(w[s].max<=v) return 0; 
    if(w[s].a==w[s].b) return w[s].max>v ? 1 : 0; 
    if(w[s].lazy) putdown(s);
    if(v>=w[s<<1].max) return cnt((s<<1)|1,v);
    else return w[s].num-w[s<<1].num+cnt(s<<1,v); 
} 

void update(int s) 
{ 
    if(w[s].a==w[s].b) return ;
    w[s].max=max(w[s<<1].max,w[(s<<1)|1].max); 
    w[s].num=w[s<<1].num+cnt((s<<1)|1,w[s<<1].max); 
} 

void build(int l,int r,int s) 
{ 
    w[s].a=l,w[s].b=r; 
    if(l==r) 
    { 
        w[s].max=(double)h[l]/l,w[s].num=1; 
        return ; 
    } 
    build(l,(l+r)>>1,s<<1),build((l+r)/2+1,r,(s<<1)|1); 
    update(s); 
} 


int x,y,val; 

void change(int s) 
{ 
    if(x>w[s].b||y<w[s].a) return ;
    if(w[s].lazy) putdown(s);
    if(x<=w[s].a&&w[s].b<=y)
    {
        w[s].max=(double)val/w[s].a,w[s].num=1;
        if(w[s].a!=w[s].b)w[s].lazy=val;
        return ;
    }
    change(s<<1),change((s<<1)|1); 
    update(s); 
} 
//.................................................................... 
int main() 
{ 
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1,a;i<=n;i++)  scanf("%d",&h[i]); 
    build(1,n,1); 
    for(int i=1,a,b,D;i<=m;i++) 
    { 
        scanf("%d%d%d",&a,&b,&D); 
        x=y=a,val=b; 
        change(1);
        if(D==1) 
        {
            scanf("%d%d%d",&x,&y,&val);
            change(1);
        }
        printf("%d\n",w[1].num);
    } 
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值