题意:给出长度为n的排列b,初始全部为0的序列a.
操作1:将a[L:R]的元素 + 1.
操作2:询问SUM(a[i]/b[i]) i=[L:R].
n,Q<=1e5.
因为b是排列并且每次操作都是将一段区间的值加1, 那么SUM(a[i]/b[[i])的答案不会超过nlogn.(q/1+q/2+q/....q/n.)
基本思路:令t[i]为还需要加多少 才能使val=(a[i]/b[i])的值增加1.
那么每次区间加操作 变为t[i]的区间减.
当t[i]为0时 需要将t[i]复原为b[i],并将val=a[i]/b[i]的值加1.
如何知道t[i]被减为0? 线段树维护t[i]最小值和区间val的和.
因为t[i]的复原操作不会超过nlogn 每次复原都走到叶子 这一部分复杂度为O(nlog^2n).最小值和区间和操作复杂度为O(nlogn).
#include <bits/stdc++.h>
#define ls (o<<1)
#define rs ((o<<1)|1)
using namespace std;
const int N=1e5+5;
int n,Q,a[N],b[N];
char op[20];
struct node{
int l,r,sum,mn,laz;
}t[N<<2];
void push_up(int o){
t[o].mn=min(t[ls].mn,t[rs].mn);
t[o].sum=t[ls].sum+t[rs].sum;
}
void build(int o,int l,int r){
t[o].l=l,t[o].r=r;
t[o].laz=t[o].sum=0;
if(l==r){
t[o].mn=b[l];
return;
}
int m=l+r>>1;
build(ls,l,m);
build(rs,m+1,r);
push_up(o);
}
void push_down(int o){
if(t[o].laz==0) return;
t[ls].laz+=t[o].laz;
t[rs].laz+=t[o].laz;
t[ls].mn-=t[o].laz;
t[rs].mn-=t[o].laz;
t[o].laz=0;
}
void update(int o,int ql,int qr){
int l=t[o].l,r=t[o].r;
if(t[o].mn>1&&ql<=l&&qr>=r){
t[o].laz++;
t[o].mn--;
return;
}
if(l==r&&t[o].mn==1){
t[o].sum++;
t[o].laz=0;
t[o].mn=b[l];
return;
}
push_down(o);
int mid=l+r>>1;
if(ql<=mid) update(ls,ql,qr);
if(qr>mid) update(rs,ql,qr);
push_up(o);
}
int query(int o,int ql,int qr){
int l=t[o].l,r=t[o].r;
if(ql<=l&&qr>=r) return t[o].sum;
int mid=l+r>>1,res=0;
if(ql<=mid) res+=query(ls,ql,qr);
if(qr>mid) res+=query(rs,ql,qr);
return res;
}
int main(){
int l,r;
while(~scanf("%d%d",&n,&Q)){
for(int i=1;i<=n;i++) scanf("%d",&b[i]);
build(1,1,n);
while(Q--){
scanf("%s%d%d",op,&l,&r);
if(op[0]=='a') update(1,l,r);
else printf("%d\n",query(1,l,r));
}
}
return 0;
}