[bzoj4293]_[PA2015]Siano(线段树+二分)

解决一个关于草的生长和收割的问题,通过使用线段树数据结构实现高效的区间查询和更新操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4293: [PA2015]Siano

Time Limit: 30 Sec Memory Limit: 256 MB
Submit: 581 Solved: 199
[Submit][Status][Discuss]

Description

农夫ByteasarByteasar买了一片nn亩的土地,他要在这上面种草。
他在每一亩土地上都种植了一种独一无二的草,其中,第i亩土地的草每天会长高aiai厘米。
ByteasarByteasar一共会进行mm次收割,其中第i次收割在第didi天,并把所有高度大于等于bibi的部分全部割去。ByteasarByteasar想知道,每次收割得到的草的高度总和是多少,你能帮帮他吗?

Input

第一行包含两个正整数n,m(1n,m500000)n,m(1≤n,m≤500000),分别表示亩数和收割次数。
第二行包含nn个正整数,其中第i个数为ai(1ai1000000)ai(1≤ai≤1000000),依次表示每亩种植的草的生长能力。
接下来mm行,每行包含两个正整数di,bi(1di10120bi1012),依次描述每次收割。
数据保证d1<d2<...<dmd1<d2<...<dm,并且任何时刻没有任何一亩草的高度超过10121012

Output

输出mm行,每行一个整数,依次回答每次收割能得到的草的高度总和。

Sample Input

4 4
1 2 4 3
1 1
2 2
3 0
4 4

Sample Output

6
6
18
0

HINT

第1天,草的高度分别为1,2,4,3,收割后变为1,1,1,1。

第2天,草的高度分别为2,3,5,4,收割后变为2,2,2,2。

第3天,草的高度分别为3,4,6,5,收割后变为0,0,0,0。

第4天,草的高度分别为1,2,4,3,收割后变为1,2,4,3。

[PA2015]Siano







解:

感觉自己是一个智障,前几天才看过一道类似的题,比赛场上疯狂RE。

今天调读入忘开long longlong long还调了仨小时。。。

其实挺简单的,按生长速度排序,可以发现在任何时刻草的高度都是按高度从小到大排序。我们每次查询修改就在线段树上二分到第一个大于该割的位置,然后查询答案,区间修改。

二分的写法需要注意,直接在线段树上二分,不要二分的时候在线段树上查询,不然会是log2log2的。

需要注意的就是,下传标记先下传赋值在下传加标记。

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
using namespace std;  
struct segment_tree{  
    int lson,rson,l,r;  
    long long num,lll,tag,plu;//lll记录这个区间最左边数的值  
}b[1000005];  

int n,m,cnt,root,zui;  
long long v[500005],qv[500005],las,d,x;  

void build(int &u,int l,int r)  
{  
    u=++cnt;  
    b[u].l=l,b[u].r=r;  
    b[u].tag=-1;  
    if(l==r) return;  
    int mid=(l+r)/2;  
    build(b[u].lson,l,mid);  
    build(b[u].rson,mid+1,r);  
}  

void update(int u)  
{  
    b[u].num=b[b[u].lson].num+b[b[u].rson].num;  
    b[u].lll=b[b[u].lson].lll;  
}  

void pushdown(int u)//先赋值,后区间加  
{  
    if(b[u].tag!=-1)  
    {  
        b[b[u].lson].num=(b[b[u].lson].r-b[b[u].lson].l+1)*b[u].tag;  
        b[b[u].rson].num=(b[b[u].rson].r-b[b[u].rson].l+1)*b[u].tag;  
        b[b[u].lson].tag=b[u].tag;b[b[u].lson].plu=0;  
        b[b[u].rson].tag=b[u].tag;b[b[u].rson].plu=0;  
        b[b[u].lson].lll=b[u].tag;b[b[u].rson].lll=b[u].tag;  
        b[u].tag=-1;  
    }  
    if(b[u].plu!=0)  
    {  
        b[b[u].lson].num+=b[u].plu*(qv[b[b[u].lson].r]-qv[b[b[u].lson].l-1]);  
        b[b[u].rson].num+=b[u].plu*(qv[b[b[u].rson].r]-qv[b[b[u].rson].l-1]);  
        b[b[u].lson].plu+=b[u].plu;  
        b[b[u].rson].plu+=b[u].plu;  
        b[b[u].lson].lll+=v[b[b[u].lson].l]*b[u].plu;  
        b[b[u].rson].lll+=v[b[b[u].rson].l]*b[u].plu;  
        b[u].plu=0;  
    }  
}  

long long query(int u,long long da,long long x)//区间赋值  
{  
    pushdown(u);  
    if(b[u].lll+v[b[u].l]*da>x)//如果最左边需要割,这个区间都要割  
    {  
        zui=min(zui,b[u].l-1);  
        b[u].num+=da*(qv[b[u].r]-qv[b[u].l-1]);  
        long long ret=b[u].num-x*(b[u].r-b[u].l+1);  
        b[u].num=x*(b[u].r-b[u].l+1);  
        b[u].tag=x;b[u].lll=x;b[u].plu=0;  
        return ret;  
    }  
    if(b[u].l==b[u].r) return 0;  
    if(b[b[u].rson].lll+v[b[b[u].rson].l]*da>=x)//如果右儿子最左边需要割,左儿子可能也需要割  
      return query(b[u].lson,da,x)+query(b[u].rson,da,x);  
    else return query(b[u].rson,da,x);//右儿子最左边都不割,左儿子肯定不割  
    update(u);  
}  

void modify(int u,int l,int r,long long da)//区间加,标记打过了几天  
{  
    pushdown(u);  
    if(l==b[u].l&&r==b[u].r)  
    {  
        b[u].num+=da*(qv[b[u].r]-qv[b[u].l-1]);  
        b[u].plu+=da;b[u].lll+=da*v[b[u].l];  
        return;  
    }  
    int mid=(b[u].l+b[u].r)/2;  
    if(r<=mid)  modify(b[u].lson,l,r,da);  
    else if(l>mid) modify(b[u].rson,l,r,da);  
    else modify(b[u].lson,l,mid,da),modify(b[u].rson,mid+1,r,da);  
    update(u);  
}  

int main()  
{  
    scanf("%d%d",&n,&m);  
    for(int i=1;i<=n;i++)  
      scanf("%lld",&v[i]);  
    sort(v+1,v+1+n);  
    for(int i=1;i<=n;i++)  
      qv[i]=qv[i-1]+v[i];  
    build(root,1,n);  
    for(int i=1;i<=m;i++)  
    {  
        zui=n;//zui用于记录区间加在哪里结束  
        scanf("%lld%lld",&d,&x);//mmp就是这个是longlong,mmp读入就longlong,mmpBZOJ交上去还不知道哪里wa了,mmp对拍了俩小时都没拍出来</span>  
        printf("%lld\n",query(root,d-las,x));  
        if(zui!=0) modify(root,1,zui,d-las);  
        las=d;  
    }  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值