1. 程式人生 > >【BZOJ】4293: [PA2015]Siano 線段樹上二分

【BZOJ】4293: [PA2015]Siano 線段樹上二分

pri line pen 二分 是否 [] long long 分享 splay

【題意】給定n棵高度初始為0的草,每天每棵草會長高a[i],m次收割,每次在d[i]天將所有>b[i]的草收割到b[i],求每次收割量。n<=500000。

【算法】線段樹上二分

【題解】按照生長速度a[]排序後,容易發現數列永遠單調。

在線段樹上的區間維護以下值:

1.最後一棵草的高度a

2.上次收割日期b

3.總的草高和c

4.總的生長速度和d

5.收割標記D和B

上傳的時候註意右區間收割晚於左區間時強制合並。

下傳的時候註意標記D和B直接覆蓋。

線段樹上二分:

1.判斷當前區間是否符合(一般為區間最右端點),否則返回r+1

2.若l=r,返回。

3.查詢左區間。

4.若左區間不符合,查詢右區間。

過程中可以順便查詢答案和修改標記。

技術分享圖片
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cctype>
#define int long long
using namespace std;
int read(){
    int s=0,t=1;char c;
    while(!isdigit(c=getchar()))if(c==-)t=-1;
    do{s=s*10+c-0;}while(isdigit(c=getchar()));
    return
s*t; } const int maxn=500010; int a[maxn],sum,n,m; struct tree{int l,r,a,b,c,d,B,D;}t[maxn*4]; void modify(int k,int B,int D){t[k].B=B;t[k].D=D;t[k].a=B;t[k].b=D;t[k].c=B*(t[k].r-t[k].l+1);} void up(int k){ t[k].a=t[k<<1|1].a; t[k].b=t[k<<1|1].b; t[k].c=t[k<<1].c+t[k<<1
].d*(t[k<<1|1].b-t[k<<1].b)+t[k<<1|1].c; } void down(int k){ if(t[k].D){ modify(k<<1,t[k].B,t[k].D);modify(k<<1|1,t[k].B,t[k].D); t[k].B=t[k].D=0; } } void build(int k,int l,int r){ t[k].l=l;t[k].r=r; if(l==r){t[k].a=0;t[k].b=0;t[k].c=0;t[k].d=a[l];return;} int mid=(l+r)>>1; build(k<<1,l,mid);build(k<<1|1,mid+1,r); up(k); t[k].d=t[k<<1].d+t[k<<1|1].d; } void plus(int k,int D,int B){sum+=t[k].c+t[k].d*(D-t[k].b)-B*(t[k].r-t[k].l+1);modify(k,B,D);} int find(int k,int D,int B){ if(t[k].a+a[t[k].r]*(D-t[k].b)<=B)return t[k].r+1; if(t[k].l==t[k].r){ plus(k,D,B); return t[k].r; } down(k); int num; if((num=find(k<<1,D,B))<=t[k<<1].r){ plus(k<<1|1,D,B); } else num=find(k<<1|1,D,B); up(k); return num; } #undef int int main(){ #define int long long n=read();m=read(); for(int i=1;i<=n;i++)a[i]=read(); sort(a+1,a+n+1); build(1,1,n); while(m--){ int D=read(),B=read(); sum=0; find(1,D,B); printf("%lld\n",sum); } return 0; }
View Code

寫復雜的題目前一定要列好程序草稿,把細節都寫清楚,程序寫出來就會比較清晰,不容易犯錯。

原來這麽復雜的題目也是可以1A的(躺

【BZOJ】4293: [PA2015]Siano 線段樹上二分