b is a static permutation of 1 to n. Initially a is filled with zeroes.
There are two kind of operations:
1.add l r: add one for $$$a_l, a_{l+1}...a_r$$$
2.query l r: query $$$\sum_{i=l}^{r}\lfloor a_i/b_i\rfloor$$$
For each test case, in the first line, two integers n,q, representing the length of a,b and the number of queries.
In the second line, n integers separated by spaces, representing permutation b.
In the following q lines, each line is either in the form 'add l r' or 'query l r', representing an operation.
$$$1\le n,q\le 100000, 1\le l\le r\le n$$$, there're no more than 5 test cases.
1 5 2 4 3
add 1 4
query 1 4
add 2 5
query 2 5
add 3 5
query 1 5
add 2 4
query 1 4
add 2 5
query 2 5
add 2 2
query 1 5
1
2
4
4
6
看到add l r和query l r,就觉得这是一道线段树,可是$$$b_i$$$的存在又破坏了区间的统一性。
根据线段树的特点,为了把add和query的复杂度降低到log(n),结点应该记录这个区间被add了几次,以及区间的和$$$\sum_{i=l}^{r}\lfloor a_i/b_i\rfloor$$$,核心问题在于怎样更新每个结点的和。
由于复杂度的原因,不能每次都重新算一遍所有结点的和,但因为每次add操作都只加1,很多结点的值都不会立刻变化;实际上区间的和的变化有这样的特点:在若干次add之前,区间和都不会改变,只有当某一项$$$a_i$$$增加到$$$b_i$$$的倍数时,区间和才会相应的+1,也就是说,在整个区间被加若干次之前,都不用更新它,只把它总共被加的次数+1;一旦某一项变成$$$a_i$$$增加到$$$b_i$$$的倍数时,区间和+1,可以认为$$$a_i$$$又恢复为0,接下来整个区间又需要若干次才会改变。如果在区间和未改变时省掉更新操作,那么复杂度将一定程度上降低。
PS:题解视频里也讲啦,总更新次数很少的
考虑这样一个标签,它记录的是,整个区间至少被加几次以后,才会让其中一项发生变化。最开始的时候,$$$a_i$$$的标签都是$$$a_i$$$,父亲结点则记录两个儿子的标签较小的那个,当标签的值变为0的时候,更新区间和,并把对应的结点的标签重新设为$$$a_i$$$,并更新路径上的所有标签;而在标签大于0的时候,则不需要对区间进行更新。
具体的来说,假设用sum[]记录区间被完整的加了几次,low[]记录区间再被加几次就需要更新,ans[]记录区间和,每次add操作,找到对应的区间i,把sum[i]++,low[i]--,如果low[i]等于0了,就向下更新,把子区间的sum加上sum[i],low减去sum[i],把所有low变为小于等于0的区间继续向下更新,直到把发生变化的那个$$$a_i$$$重设为0,ans[]++,然后在回溯的时候,更新路径上的所有low[]和ans[]。
线段树写的有点烂。。。勉强过了
#include<stdio.h>
#include <cstring>
#define N_max 100005
typedef long long LL;
#define min(a,b) ((a)<(b)?(a):(b))
int lg[N_max << ], rg[N_max << ]
, low[N_max << ]//还要加几次
, sum[N_max << ]//已经加几次
, b[N_max << ]//bi
,ans[N_max<<]//区间的和
; #define lid(x) (x<<1)
#define rid(x) (x<<1|1)
void build(int id,int cl,int cr)
{
lg[id] = cl;
rg[id] = cr;
if (cl == cr)//build的时候完成输入,并初始化low为bi
{
scanf("%d", b + id);
low[id] = b[id]; return;
}
int left = lid(id),right=rid(id),cm= (cl + cr) >> ;
build(left, cl, cm);
build(right, cm + , cr);
low[id] = min(low[left], low[right]);//结点记录最小的low
} //low为0时,向下更新,直接处理整个区间
void addup(int id,int v)
{
sum[id] += v;
low[id] -= v;
if(lg[id]==rg[id])//遇到叶子结点
{
ans[id] += (sum[id]/ b[id]);
low[id] =b[id]- (sum[id] % b[id]);
sum[id] %=b[id] ;
}
if(low[id]<=)//low<=0说明区间内的某个数发生变化,需要继续向下更新
{
addup(lid(id), sum[id]);
addup(rid(id), sum[id]);
sum[id] = ;
ans[id] = ans[lid(id)] + ans[rid(id)];
low[id]=min(low[lid(id)], low[rid(id)]);
}
} inline void pushdown(int id)
{
if (sum[id])
{
sum[lid(id)] += sum[id];
low[lid(id)] -= sum[id];
sum[rid(id)] += sum[id];
low[rid(id)] -= sum[id];
sum[id] = ;
}
}
void add(int id,int cl,int cr,int v)
{
if (lg[id] == rg[id])//叶子结点
{
low[id]--; sum[id]++;//当进入叶子结点时,sum的值其实就是ai
ans[id] += (sum[id] / b[id]);//ai中有几个bi,ans就应该加几
low[id] = b[id] - (sum[id] % b[id]);//low变为bi-ai%bi
sum[id] %= b[id];//ai变为ai%bi
return ;
}
int left = lid(id), right = rid(id);
if(lg[id]==cl&&rg[id]==cr)//遇到匹配的区间
{
low[id]--; sum[id]++;
if(low[id]>)return ;
if ( low[id]<=)//如果low<=0,说明应该往下更新
{
//下面的更新一定是整个结点的,调用addup而不是add
addup(left, sum[id]);
addup(right, sum[id]);
//回溯时更新low和ans
low[id] = min(low[left], low[right]);
ans[id] = ans[left] + ans[right];
//因为sum已经加到子区间上了,把sum改为0
sum[id] =;
return ;
}
} //匹配区间结点
int mid = (lg[id] + rg[id]) >> ;
pushdown(id);
if (cr <= mid)
{
add(left, cl, cr, );
ans[id] = ans[left] + ans[right];
low[id] = min(low[left], low[right]);
return;
}
else if (cl > mid)
{
add(right, cl, cr, );
ans[id] = ans[left] + ans[right];
low[id] = min(low[left], low[right]);
return;
}
else
{
add(left, cl, mid,);
add(right, mid + , cr,);
ans[id] = ans[left] + ans[right];
low[id] = min(low[left], low[right]);
return;
}
} int qry(int id,int cl,int cr)
{
if (lg[id] == cl&&rg[id] == cr)
{
return ans[id];
} int mid = (lg[id] + rg[id]) >> ;
pushdown(id);
if (cr <= mid)
{
return qry(lid(id), cl, cr);
}
else if (cl > mid)
{
return qry(rid(id), cl, cr) ;
}
else
{
return qry(lid(id), cl, mid)+qry(rid(id), mid + , cr);
}
} int n,q;
char str[];
int main() { while (~scanf("%d %d" , &n,&q))
{
memset(lg, , sizeof lg);
memset(rg, , sizeof rg);
memset(low, , sizeof low);
memset(sum, , sizeof sum);
memset(ans, , sizeof ans);
build(, ,n);
int a1, a2;
for(int i=;i<q;++i)
{
scanf("%s", str);
scanf("%d %d", &a1, &a2);
if (str[] == 'a')
add(, a1, a2,);
else if (str[] == 'q')
printf("%d\n",qry(, a1, a2));
}
}
return ;
}