Siano「线段树」
题目描述
农夫 Byteasar 买了一片 \(n\) 亩的土地,他要在这上面种草。
他在每一亩土地上都种植了一种独一无二的草,其中,第 \(i\) 亩土地的草每天会长高 \(a_i\) 厘米。
Byteasar 一共会进行 \(m\) 次收割,其中第 \(i\) 次收割在第 \(d_i\) 天,并把所有高度大于等于 \(b_i\) 的部分全部割去。
Byteasar 想知道,每次收割得到的草的高度总和是多少,你能帮帮他吗?
输入格式
第一行包含两个正整数 \(n,m\),分别表示亩数和收割次数。
第二行包含 \(n\) 个正整数,其中第 \(i\) 个数为 \(a_i\),依次表示每亩种植的草的生长能力。
接下来 \(m\) 行,每行包含两个正整数 \(d_i,b_i\),依次描述每次收割。
输出格式
输出 \(m\) 行,每行一个整数,依次回答每次收割能得到的草的高度总和。
输入输出样例
输入 #1
4 4
1 2 4 3
1 1
2 2
3 0
4 4
输出 #1
6
6
18
0
说明/提示
对于 \(100\%\) 的数据,\(n,m\le 5\times 10^5\),\(1\le a_i\le 10^6\),\(1\le d_i,b_i\le 10^{12}\)。
数据保证 \(d_1<d_2<...<d_m\),并且任何时刻没有任何一亩草的高度超过 \(10^{12}\)。
思路分析
- 首先这题有一个很神奇的性质,就是长得快得一定长得高,不论有没有被割掉,那么在将生长速度排序以后,每一次割掉的一定是一个区间,而且还是一个后缀区间
- 所以就可以很方便地使用线段树处理了,因为每一次都可能有被割的,也会有没被割的,所以将其分开处理。
- 这时候我们要做的就是找到生长速度最小的会被割掉的点,这在线段树上也很方便处理。在线段树中记录一些其包含区间的最高值,寻找时只需判断一下左右子树的最大值然后决定是继续向左还是向右。
另外也有可能找不到,返回的时候处理一下就好了
\(Code\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define R register
#define N 500010
#define ll long long
using namespace std;
inline ll read(){
ll x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,a[N];
ll sum[N];
struct Segment_Tree{
ll het,clock,sum,est;//分别对应高度的tag,生长天数的tag,区间和,和最高值
}tr[N<<2];
#define ls rt<<1
#define rs rt<<1|1
void build(int rt,int l,int r){
tr[rt].het = -1;//初始化为-1,因为可能会有高度为0的时候,代表没有被割
if(l==r)return;
int mid = (l+r)>>1;
build(ls,l,mid);
build(rs,mid+1,r);
}
void cut(int rt,int l,int r,ll val){
tr[rt].het = tr[rt].est = val;//被割以后高度相同
tr[rt].sum = 1ll*(r-l+1)*val;
tr[rt].clock = 0;//注意清空生长天数,因为这时是一个新起点
}
void grow(int rt,int l,int r,ll days){
tr[rt].clock += days;//这个直接区间加就好了
tr[rt].sum += 1ll*(sum[r]-sum[l-1])*days;
tr[rt].est += 1ll*a[r]*days;
}
void pushdown(int rt,int l,int r){
int mid = (l+r)>>1;
if(tr[rt].het!=-1){//说明需要割
cut(ls,l,mid,tr[rt].het);
cut(rs,mid+1,r,tr[rt].het);
tr[rt].het = -1;
}
if(tr[rt].clock){//说明没被割
grow(ls,l,mid,tr[rt].clock);
grow(rs,mid+1,r,tr[rt].clock);
tr[rt].clock = 0;
}
}
ll modify(int rt,int l,int r,int s,int t,ll val){//可以在修改的时候就得出答案
if(s<=l&&t>=r){
ll tmp = tr[rt].sum;
cut(rt,l,r,val);
return tmp - tr[rt].sum;//割前的高度-割完以后的高度
}
ll res = 0;
int mid = (l+r)>>1;
pushdown(rt,l,r);
if(s<=mid)res += modify(ls,l,mid,s,t,val);
if(t>mid)res += modify(rs,mid+1,r,s,t,val);
tr[rt].sum = tr[ls].sum + tr[rs].sum;
tr[rt].est = tr[rs].est;
return res;
}
int find(int rt,int l,int r,ll val){//寻找会被割的最小左端点
if(l==r)return tr[rt].sum < val ? n+1 : l;
int mid = (l+r)>>1;
pushdown(rt,l,r);
if(tr[ls].est>=val)return find(ls,l,mid,val);
else return find(rs,mid+1,r,val);
}
int main(){
n = read(),m = read();
for(R int i = 1;i <= n;i++)a[i] = read();
sort(a+1,a+1+n);
for(R int i = 1;i <= n;i++)sum[i] = sum[i-1]+a[i];
build(1,1,n);
ll pre = 0;
for(R int i = 1;i <= m;i++){
ll d = read(),b = read();
grow(1,1,n,d-pre);
pre = d;
printf("%lld\n",modify(1,1,n,find(1,1,n,b),n,b));
}
return 0;
}