莫队略解

command_block

2018-12-09 11:50:20

Personal

相关知识 :[分块相关杂谈](https://www.luogu.com.cn/blog/command-block/fen-kuai-xiang-guan-za-tan) # 1.经典莫队 - **抽象概论** : 经典莫队适用于这样的一类问题 : 给出一个长度为 $n$ 的序列,对 $q$ 个区间进行询问。 由区间 $[l,r]$ 的答案可以快速得到区间 $[l,r+1],[l,r-1],[l-1,r],[l+1,r]$ 的答案。即 : 端点可以快速移动。 允许离线。(下面的所有问题若未加说明均默认允许离线) - $\bf\color{green}\Delta$ **例题①** : [P4462 [CQOI2018]异或序列](https://www.luogu.com.cn/problem/P4462) **题意** : 给出一个序列和参数 $k$ ,每次询问一个区间内有多少子区间的异或和为 $k$。 $n,m,k,a_i\leq 10^5$。 ------------ 首先做异或前缀和,问题就变为了区间内选取一个对子,使得异或值为 $k$ 的方案数。 对于计算“对子”贡献的问题,可以每次考虑新加入元素和旧元素之间的贡献。 记录 $s[x]$ 为目前区间内 $x$ 的出现次数,假设新加入了 $y$ 那么能够异或出 $k$ 的 $x=k\ {\rm xor}\ y$ ,贡献就是 $s[k\ {\rm xor}\ y]$。然后将 $s[y]$ 加一。 当去除某个元素时,也容易找到逆操作。 我们发现,通过加删操作,我们可以在 $O(1)$ 内从区间 $[l,r]$ 的答案得到 $[l-1,r],[l,r-1],[l+1,r],[l,r+1]$ ,将区间端点移动。 若上一次回答 $[L,R]$ ,这一次回答 $[l,r]$ ,指针需要跳动 $|L-l|+|R-r|$ 次。 我们发现,询问的回答顺序会影响复杂度。由于允许离线处理,我们可以任意安排回答顺序。 如何找到最优的回答顺序? 这可以转化成**平面曼哈顿距离最短哈密顿路**问题。 精确地解决这个问题较为困难,不过我们只需要找到一个近似的做法来保证复杂度 : **分块**。 具体方法 : 对原序列分块,左端点在同一块里的,按照右端点排序,否则按照左端点排序。 例子: ```cpp 排序前 +-+ +-----+ +-+ +---------+ +---------+ 1 2 3|4 5 6|7 8 9| 排序后(三个数为一块) +-+ +---------+ +---------+ +-----+ +-+ 1 2 3|4 5 6|7 8 9| ``` 肉眼可见,时间节省了很多。 - **复杂度证明** : 设每块的大小为 $B$ ,数列长为 $n$ ,询问个数为 $q$ 。 这些询问最多被分成 $n/B$ 块,块内右端点有序,而且安排在一起回答。 $r$ 指针不会往回跳,所以一个块复杂度 $O(n)$ ,共 $O(n/B)$ 块复杂度为 $O(n^2/B)$ 每个块内的左端点无序,但是它们相距不是很远,最多相距 $O(B)$ 那么远。 所以每个询问左端点最多跳 $O(B)$ 次,总的复杂度 $O(qB)$ 综上所述,总共的复杂度是 $O(qB+n^2/B)$。 - 如果令 $t=\sqrt{n}$ ,得复杂度是 $O((n+q)\sqrt{n})$ - 如果令 $t=\dfrac{n}{\sqrt{q}}$ ,得复杂度是 $O(n\sqrt{q})$ (总是优于楼上) What? 复杂度从 $O(n^2)$ 一下变成了 $O(n\sqrt{q})$ ? ~~没错,离线真的可以为所欲为。~~ 事实上,这个数量级也可以证明为平面最短哈密顿路的下界。 上代码,初学者要注意细节。 - 指针移动和贡献计算的先后顺序。 - 先执行将区间变大的操作,避免出现 $l>r$ 的情况(在某些题中可能导致错误)。 ```cpp #include<algorithm> #include<cstdio> #include<cmath> #define ll long long #define MaxN 100500 using namespace std; struct Data{int l,r,p;}b[MaxN]; int BS; bool cmp(const Data &A,const Data &B) {return A.l/BS==B.l/BS ? A.r<B.r : A.l<B.l;} int n,m,k,a[MaxN],s[MaxN<<1]; ll ret[MaxN]; int main() { scanf("%d%d%d",&n,&m,&k); BS=n/sqrt(m)+1; for (int i=1;i<=n;i++){ scanf("%d",&a[i]); a[i]^=a[i-1]; } for (int i=1;i<=m;i++){ scanf("%d%d",&b[i].l,&b[i].r); b[i].l--;b[i].p=i; }sort(b+1,b+m+1,cmp); int l=1,r=0;ll ans=0; for (int i=1;i<=m;i++){ while(l>b[i].l){ l--; ans+=s[k^a[l]]; s[a[l]]++; } while(r<b[i].r){ r++; ans+=s[k^a[r]]; s[a[r]]++; } while(l<b[i].l){ s[a[l]]--; ans-=s[k^a[l]]; l++; } while(r>b[i].r){ s[a[r]]--; ans-=s[k^a[r]]; r--; }ret[b[i].p]=ans; } for (int i=1;i<=m;i++) printf("%lld\n",ret[i]); return 0; } ``` 可以看到,莫队算法的实现是较为简洁的。 - $\bf\color{green}\Delta$ **初级附加练习** : [P1494 [国家集训队]小Z的袜子](https://www.luogu.org/problemnew/show/P1494) [P2709 小B的询问](https://www.luogu.org/problemnew/show/P2709) - **卡常技巧** : 奇偶块排序 仔细分析两个指针的行为。 在某一块内,$r$ 指针从左到右跳一整遍,$l$ 指针每次在块内乱动,而在两个块间切换时,$r$ 指针又要回到序列开头。 我们可以在相邻的两块中,前一块按照 $r$ 升序排序,后一块按照 $r$ 降序排序,这样 $r$ 指针就不需要回头了。 - $\bf\color{green}\Delta$ **例题②** : 『 三数列问题 』 定义一个可重集合的权值为 : 其中的同色无序对数。 给出三个数列 $A,B,C$ ,每次给出三个数 $l_A,l_B,l_C$ ,询问 $A[1,l_a],B[1,l_b],C[1,l_C]$ 三个前缀组成的可重集的权值。 $n\leq 5\times 10^4,q\leq 10^5$。 ------------ > **子问题** : 维护一个可重集,支持插入删除,回答权值。 > 类似 `P1494` ,使用桶记下每个元素的出现次数,不难实现 $O(1)$ 插入删除。 如果把询问 $(l_A,l_B,l_C)$ 看做三维空间内的点,我们就可以利用上述算法每次移动一步。 若要寻找耗时最少的回答顺序,则是三维曼哈顿距离最短哈密顿路问题。仍然可以使用分块的方法。 将整个 $n\times n\times n$ 的空间均匀地分成若干立方体区域,假设边长为 $B$。 那么,在任意两个在同一块内的询问之间跳转的代价,和在相邻两块的询问之间跳转的代价都是 $O(B)$ 的。 如果按照某种顺序依次访问各个块(这显然能够做到),复杂度则是 $O(\text{块数}*B)$ ,块数是 $(n/B)^3$。 总复杂度则是 $O(n^3/B^2+qB)$ ,令 $n^3/B^2=qB$ 解得 $B=n/\sqrt[3]{q}$ ,最优复杂度为 $O(nq^{2/3})$ 。 上面只是复杂度的理论分析,具体实现可见下文“带修莫队”。 扩展 : $k$ 维莫队的复杂度可以做到 $O(nq^{1-1/k})$。 - $\bf\color{green}\Delta$ **例题③** : [P5268 [SNOI2017]一个简单的询问](https://www.luogu.com.cn/problem/P5268) **题意** : 给出一个序列和若干形为 $(l_1,r_2,l_2,r_2)$ 的询问,求下列式子的值 $$\sum\limits_{x}\text{get}(l_1,r_1,x)\times \text{get}(l_2,r_2,x)$$ $\text{get}(l,r,x)$ 表示计算区间 $[l,r]$ 中,数字 $x$ 出现了多少次。 $n,q\leq 5\times 10^4$。 不难发现,当 $l_1,r_1,l_2,r_2$ 之一移动一步时,只有一个 $x$ 的贡献会改变,且利用桶容易计算。 那么,我们就得到了一个四维莫队的 $O(nq^{3/4})$ 搞笑做法。但这也告诉我们四个指针同时对付是没有出路的。 考虑差分,设 $c(l,x)=\text{get}(1,l,x)$ ,则有 $get(l,r,x)=c(1,r,x)-c(1,l-1,x)$。 将欲求式子改写成 : $$\sum\limits_{x}\big(c(r_1,x)-c(l_1-1,x)\big)\big(c(r_2,x)-c(l_2-1,x)\big)$$ 拆开括号后可以得到 $4$ 个形如下式的子问题。 $$\sum\limits_{x}c(r_1,x)c(r_2,x)$$ 这就是二维莫队可以解决的了,复杂度 $O(n\sqrt{m})$。 ```cpp #include<algorithm> #include<cstdio> #include<cmath> #define ll long long #define MaxN 50500 using namespace std; int read(){} struct Data{int p1,p2,p,op;}b[MaxN*4]; int BS; bool cmp(const Data &A,const Data &B) {return A.p1/BS==B.p1/BS ? A.p2<B.p2 : A.p1<B.p1;} int c1[MaxN],c2[MaxN],a[MaxN]; ll ret[MaxN]; int n,m,q; int main() { n=read(); for (int i=1;i<=n;i++)a[i]=read(); q=read(); for (int i=1,l1,l2,r1,r2;i<=q;i++){ l1=read();r1=read(); l2=read();r2=read(); b[++m]=(Data){l1-1,l2-1,i,1}; b[++m]=(Data){r1,l2-1,i,-1}; b[++m]=(Data){l1-1,r2,i,-1}; b[++m]=(Data){r1,r2,i,1}; }BS=n/sqrt(m)+1; sort(b+1,b+m+1,cmp); int p1=0,p2=0;ll ans=0; for (int i=1;i<=m;i++){ while(p1<b[i].p1){ ans+=c2[a[++p1]]; c1[a[p1]]++; }while(p1>b[i].p1){ c1[a[p1]]--; ans-=c2[a[p1--]]; } while(p2<b[i].p2){ ans+=c1[a[++p2]]; c2[a[p2]]++; }while(p2>b[i].p2){ c2[a[p2]]--; ans-=c1[a[p2--]]; }ret[b[i].p]+=b[i].op*ans; } for (int i=1;i<=q;i++) printf("%lld\n",ret[i]); return 0; } ``` 类似的题 : [P4689 [Ynoi2016]这是我自己的发明](https://www.luogu.com.cn/problem/P4689) -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ------------ # 2.带修莫队 前面我们用莫队解决的是一类无修改的“静态”问题,现在我们考虑修改。 - $\bf\color{green}\Delta$ **例题** : [P1903 [国家集训队]数颜色 / 维护队列](https://www.luogu.com.cn/problem/P1903) **题意** : 带修区间数颜色问题。 我们把一个询问描述为 $(l,r,t)$ : 前 $t$ 个修改生效后,区间 $[l,r]$ 的颜色数。 考虑如何在这个三维空间内移动, $l,r$ 的移动方法我们是熟悉的,下面来研究 $t$。 考虑一个修改对当前状态 $[l,r]$ 的贡献,如果不是改了 $[l,r]$ 内的位置,显然无影响。否则相当于一次加元素和一次删元素。 接下来就是三维莫队的活了。 设块大小为 $B$。 当左端点不在一个块内,则比较左端点。 反之则考虑右端点,如果不在一个块内,则比较左端点。反之比较时间。 (意思是,若不在一个块内,优先向左走,次之向右走,最末沿时间轴走) 根据前文的分析,令 $q=n/q^{1/3}$ 可得复杂度为 $O(nq^{2/3})$。 ```cpp #include<algorithm> #include<cstdio> #define MaxN 140000 #define MaxS 1000500 using namespace std; int read(){ int X=0;char ch=0; while(ch<48||ch>57)ch=getchar(); while(ch>=48&&ch<=57)X=X*10+(ch^48),ch=getchar(); return X; } bool readc(){ char ch=getchar(); while('Q'!=ch&&'R'!=ch)ch=getchar(); return 'Q'==ch; } struct Data {int l,r,pos,t;}b[MaxN]; struct Ord {int pos,x,las;}c[MaxN]; int a[MaxN],bl[MaxN],o[MaxS],ret[MaxN]; bool cmp(const Data &A,const Data &B){ return bl[A.l]==bl[B.l] ? (bl[A.r]==bl[B.r] ? A.t<B.t : A.r<B.r) : A.l<B.l; } #define add(p) if (++o[a[p]]==1)ans++; #define del(p) if (--o[a[p]]==0)ans--; char ord;int n,m,BS=10,tb,tc; int main() { n=read();m=read(); while(1ll*BS*BS*BS<=1ll*n*n*5)BS++; for (int i=1;i<=n;i++) {a[i]=read();bl[i]=i/BS;} for (int i=1;i<=m;i++){ if (readc()){ b[++tb].l=read();b[tb].r=read(); b[tb].pos=tb;b[tb].t=tc; }else{ c[++tc].pos=read(); c[tc].las=a[c[tc].pos]; a[c[tc].pos]=c[tc].x=read(); } }sort(b+1,b+tb+1,cmp); int l=1,r=0,tim=tc,ans=0; for (int i=1,p;i<=tb;i++){ while(tim<b[i].t){ p=c[++tim].pos; if (l<=p&&p<=r)del(p); a[p]=c[tim].x; if (l<=p&&p<=r)add(p); }while(tim>b[i].t){ p=c[tim].pos; if (l<=p&&p<=r)del(p); a[p]=c[tim--].las; if (l<=p&&p<=r)add(p); }while(l<b[i].l)del(l++); while(l>b[i].l)add(--l); while(r>b[i].r)del(r--); while(r<b[i].r)add(++r); ret[b[i].pos]=ans; }for (int i=1;i<=tb;i++) printf("%d\n",ret[i]); return 0; } ``` 由于修改的常数较大,建议把块大小略微调大。加强了数据导致卡常,不开 $O_2$ 过不了。 -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- ------------ # 3.回滚莫队 - $\bf\color{green}\Delta$ **例题** : [P5906 【模板】回滚莫队&不删除莫队](https://www.luogu.com.cn/problem/P5906) 回滚莫队用于解决这样一类问题 : 我们向区间中加入元素相对容易,而删除元素较为困难。 也就是说,区间 $[l,r]$ 容易快速转移到 $[l-1,r]$ 或者 $[l,r+1]$。 比如此题中,我们只需对每个颜色记录最靠左和最靠右的出现,就能资瓷加入元素。 考虑巧妙地加入元素来规避删除操作。 先暴力处理两个端点在同一块内的操作。 观察到同一块中, $r$ 指针只会向右跳(加入),这部分不会产生删除操作。 问题就在于 $l$ 指针乱跳可能出现删除。 我们可以一开始将 $l$ 置为本块的右边界。 回答每个询问时,先扩展 $r$ 指针,然后再移动 $l$ ,最后将 $l$ 回滚(撤销操作),回到右边界。 (本质上是需要资瓷快速计算临时的 $O(\sqrt{n})$ 个加入的贡献) 这样子 $l$ 指针常数大了 2~3 倍,而且跨越两块需要清空,写得不好复杂度可能变为$O((n+m)\sqrt{n})$。 ```cpp #include<algorithm> #include<cstdio> #define MaxN 200500 using namespace std; inline int read(){} int BS,bl[MaxN]; struct Data {int l,r,tl,tr,p;}b[MaxN]; bool cmp(const Data &A,const Data &B) {return bl[A.l]==bl[B.l] ? A.r<B.r : A.l<B.l;} int x[MaxN],a[MaxN],l[MaxN],r[MaxN],sl[MaxN],sr[MaxN] ,n,q,m,ret[MaxN],ans; void clr() {for (int k=1;k<=m;k++){l[k]=n;r[k]=0;}} int main() { n=read();for (BS=5;BS*BS*2<=n;BS++); for (int i=1;i<=n;i++){ x[i]=a[i]=read(); bl[i]=i/BS; }sort(x+1,x+n+1); m=unique(x+1,x+n+1)-x-1; for (int i=1;i<=n;i++) a[i]=lower_bound(x+1,x+m+1,a[i])-x; q=read(); for (int i=1;i<=q;i++){ b[i].l=read();b[i].r=read(); b[i].p=i; }sort(b+1,b+q+1,cmp);clr(); int tl=(1/BS+1)*BS-1,tr=tl-1; for (int i=1;i<=q;i++){ if (bl[b[i].l]!=bl[b[i-1].l]){ tl=(b[i].l/BS+1)*BS-1; tr=tl-1;ans=0;clr(); } if (bl[b[i].l]==bl[b[i].r]){ int sav=0; for (int k=b[i].l;k<=b[i].r;k++)sr[a[k]]=k; for (int k=b[i].l;k<=b[i].r;k++) sav=max(sav,sr[a[k]]-k); ret[b[i].p]=sav; continue; } while(tr<b[i].r){ ++tr; if (l[a[tr]]==n)l[a[tr]]=tr; r[a[tr]]=tr; ans=max(ans,tr-l[a[tr]]); } int sav=ans,sp=tl; for (int k=b[i].l;k<=tl;k++) {sl[a[k]]=l[a[k]];sr[a[k]]=r[a[k]];} while(b[i].l<tl){ --tl; l[a[tl]]=tl; if (r[a[tl]]==0)r[a[tl]]=tl; ans=max(ans,r[a[tl]]-tl); }ret[b[i].p]=ans; tl=sp;ans=sav; for (int k=b[i].l;k<=tl;k++) {l[a[k]]=sl[a[k]];r[a[k]]=sr[a[k]];} } for (int i=1;i<=q;i++) printf("%d\n",ret[i]); return 0; } ``` # 4.树上莫队 特指解决树链问题的莫队。显然,这个问题强于区间莫队。 - $\bf\color{green}\Delta$ **例题①** : [SP10707 COT2 - Count on a tree II](https://www.luogu.com.cn/problem/SP10707) **题意** : 树链数颜色。 ------------ - **全dfs序** : 在 `dfs` 每次到达某个点时(入栈),在序列中加入该点。离开某个点时,再加入一次,得到长为 $2n$ 的序列。 称 $dfn[u]$ 为 $u$ 第一次被写入欧拉序的位置,$out[u]$ 为第二次被写入欧拉序的位置。 - **结论** : 路径 $u,v$ (不妨设 $dfn[u]\leq dfn[v]$ )中的点集为 : 全 dfs 序列 $\big[out[u],dfn[v]\big]$ 之间**恰好出现一次**的点并上 ${\rm lca}(u,v)$ 。 - **证明** : $S=\big[out[u],dfn[v]\big]$ 中的全 dfs 序其实就是记录了在 $u,v$ 之间 `dfs` 的过程。 令 $t={\rm lca}(u,v)$ ,那么 $u,t$ 之间的点的入栈操作均不在 $S$ 中,而出栈操作都在 $S$ 中。 对于 $v,t$ 之间的点,出栈操作均不在 $S$ 中,而入栈操作都在 $S$ 中。 而之间经过的分支不属于 $u,v$ 之间的路径,相应的,其出入栈都在 $S$ 内。 对于 $t$ ,则需要特殊考虑。 由于这个性质,我们只需将第二次加入看做删除即可。这样,我们就将树链问题转化为了区间问题。 ```cpp ``` - **例题②** : 在路上了 ------------ 我们可以用树上回滚莫队将其解决。但回滚莫队不支持删除,无法使用前文中全 dfs 序的做法。 于是,我们考虑另一种转化。 - **综合练习** : [P4074 [WC2013] 糖果公园](https://www.luogu.com.cn/problem/P4074) # 5.二次离线莫队 - **例题** : [P5047 [Ynoi2019 模拟赛] Yuno loves sqrt technology II](https://www.luogu.com.cn/problem/P5047)、 **题意** : 查询区间逆序对数。允许离线。 $n,q\leq 10^5$ ,时限$\texttt{250ms}$ ,空限$\texttt{32Mb}$。 首先有一个显然的 $O(n\sqrt{n}\log n)$ 暴力。 用莫队维护,并用树状数组维护当前集合。每次加入时计算当前数与其他数之间的贡献,只需查询当前集合中 $\leq x$ 的数的个数。 该算法需要 $O(n\sqrt{n})$ 次单点修改与前缀查询,故无法使用简单的根号平衡来解决。 注意到,我们在指针移动时所需的询问形如 : “在区间 $[l,r]$ 内有多少个数 $\leq k$”,而这可以差分为“在前缀 $[1,r]$ 内有多少个数 $\leq k$”。 我们可以用可持久化分块($O(\sqrt{n})$ 修改 $O(1)$ 查询)来支持 $O(1)$ 求解这个问题。这样可以做到时空复杂度 $O(n\sqrt{n})$ ,但常数较大。 由于莫队处理的是离线问题,我们可以将需要的 $O(n\sqrt{n})$ 个询问再次离线下来,然后按照 $r$ 排序,用普通值域分块一次处理(扫描线),这样就不需要可持久化了。很遗憾,记下询问所需的空间复杂度仍然是 $O(n\sqrt{n})$ 的。 为了优化空间复杂度(以及减小时间常数),我们进一步观察莫队产生的询问的特殊性质 : 记 $(r,x)=$ “在前缀 $[1,r]$ 内 $\leq x$ 的数的数目” - $l$ 指针向左移动到 $l'$ : $[l,r],[l-1,r],[l-2,r]\dots[l',r]$ 其中的贡献为 $\sum\limits_{i\in[l',l)}(r,A_i-1)-(i,A_i-1)$ 。 向右移动只需将贡献取反。 - $r$ 指针向右移动到 $r'$ : $[l,r],[l,r+1],[l,r+2]\dots[l,r']$ 其中的贡献为 $\sum\limits_{i\in(r,r']}(i-l+1)-\big((i-1,A_i)-(l-1,A_i)\big)$ 。 对于 $\sum\limits_{i\in(r,r']}(i-l+1)$ 部分容易计算。 向左移动只需将贡献取反。 观察其中的贡献形式,可以分类为以下四种 : - $A:(l,r,x)$ 表示 $\sum\limits_{i\in[l,r]}(x,A_i-1)$ - $B:(l,r)$ 表示 $\sum\limits_{i\in[l,r]}(i,A_i-1)$ ,这个可以事先预处理。 - $C:(l,r)$ 表示 $\sum\limits_{i\in[l,r]}(i-1,A_i)$ ,这个也容易事先预处理。 - $D:(l,r,x)$ 表示 $\sum\limits_{i\in[l,r]}(x,A_i)$ 只有 $A,D$ 两种需要扫描线。 我们只需要将 $(l,r,x)$ 的三元组挂在 $l$ 处,动态维护目前生效的三元组,若期限 $r$ 到了,就将其删除。 于是空间复杂度优化到了 $O(n)$ ,时间常数也大为减小。 ```cpp #include<algorithm> #include<cstdio> #include<vector> #include<cmath> #define ll long long #define pb push_back #define MaxN 100500 using namespace std; int n; struct SumDS { #define lbit(x) (x&-x) int t[MaxN]; void add(int p) {while(p<=n){t[p]++;p+=lbit(p);}} int qry(int p){ int ret=0; while(p){ret+=t[p];p^=lbit(p);} return ret; } }T; struct SumDS2 { int ob[505],o[MaxN],BS; void add(int p){ int bp=p/BS+1,br=bp*BS; for (int i=bp;i<=BS;i++)ob[i]++; for (int i=p;i<br;i++)o[i]++; } inline int qry(int p) {return ob[p/BS]+o[p];} }T2; ll ans[MaxN]; struct Data2{int l,r,c,d,p;}; vector<Data2> s[MaxN]; struct Data{int l,r,p;}b[MaxN]; ll s1[MaxN],s2[MaxN]; int q,BS; bool cmp(const Data &A,const Data &B) {return A.l/BS==B.l/BS ? ((A.l/BS)&1)^(A.r<B.r) : A.l<B.l;} void MoQ() { sort(b+1,b+q+1,cmp); int l=1,r=0; for (int i=1;i<=q;i++){ if (b[i].l<l){ ans[b[i].p]-=s1[l-1]-s1[b[i].l-1]; s[r].pb((Data2){b[i].l,l-1,1,-1,b[i].p}); l=b[i].l; } if(r<b[i].r){ ans[b[i].p]+=1ll*(r+b[i].r+1-2*l)*(b[i].r-r)/2-(s2[b[i].r]-s2[r]); s[l-1].pb((Data2){r+1,b[i].r,1,0,b[i].p}); r=b[i].r; } if(l<b[i].l){ ans[b[i].p]+=s1[b[i].l-1]-s1[l-1]; s[r].pb((Data2){l,b[i].l-1,-1,-1,b[i].p}); l=b[i].l; } if(b[i].r<r){ ans[b[i].p]-=1ll*(r+b[i].r+1-2*l)*(r-b[i].r)/2-(s2[r]-s2[b[i].r]); s[l-1].pb((Data2){b[i].r+1,r,-1,0,b[i].p}); r=b[i].r; } } } int a[MaxN],x[MaxN]; int main() { scanf("%d%d",&n,&q); for (int i=1;i<=n;i++) {scanf("%d",&a[i]);x[i]=a[i];} sort(x+1,x+n+1); for (int i=1;i<=n;i++) a[i]=lower_bound(x+1,x+n+1,a[i])-x; for (int i=1;i<=n;i++){ s2[i]=s2[i-1]+T.qry(a[i]); T.add(a[i]); s1[i]=s1[i-1]+T.qry(a[i]-1); } for (int i=1;i<=q;i++){ scanf("%d%d",&b[i].l,&b[i].r); b[i].p=i; } BS=1.0*n/sqrt(q)+5; MoQ(); T2.BS=sqrt(n)+1; for (int i=1;i<=n;i++){ T2.add(a[i]); for (int k=0;k<s[i].size();k++){ Data2 u=s[i][k]; for (int j=u.l;j<=u.r;j++) ans[u.p]+=u.c*T2.qry(a[j]+u.d); } } for (int i=1;i<=q;i++)ans[b[i].p]+=ans[b[i-1].p]; for (int i=1;i<=q;i++)printf("%lld\n",ans[i]); return 0; } ```