2018 杭电多校2 - Naive Operations
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 << 2], rg[N_max << 2] , low[N_max << 2]//还要加几次 , sum[N_max << 2]//已经加几次 , b[N_max << 2]//bi ,ans[N_max<<2]//区间的和 ; #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) >> 1; build(left, cl, cm); build(right, cm + 1, 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]<=0)//low<=0说明区间内的某个数发生变化,需要继续向下更新 { addup(lid(id), sum[id]); addup(rid(id), sum[id]); sum[id] = 0; 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] = 0; } } 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]>0)return ; if ( low[id]<=0)//如果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] =0; return ; } } //匹配区间结点 int mid = (lg[id] + rg[id]) >> 1; pushdown(id); if (cr <= mid) { add(left, cl, cr, 1); ans[id] = ans[left] + ans[right]; low[id] = min(low[left], low[right]); return; } else if (cl > mid) { add(right, cl, cr, 1); ans[id] = ans[left] + ans[right]; low[id] = min(low[left], low[right]); return; } else { add(left, cl, mid,1); add(right, mid + 1, cr,1); 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]) >> 1; 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 + 1, cr); } } int n,q; char str[10]; int main() { while (~scanf("%d %d" , &n,&q)) { memset(lg, 0, sizeof lg); memset(rg, 0, sizeof rg); memset(low, 0, sizeof low); memset(sum, 0, sizeof sum); memset(ans, 0, sizeof ans); build(1, 1,n); int a1, a2; for(int i=0;i<q;++i) { scanf("%s", str); scanf("%d %d", &a1, &a2); if (str[0] == 'a') add(1, a1, a2,1); else if (str[0] == 'q') printf("%d\n",qry(1, a1, a2)); } } return 0; }