题解 P5047 【[Ynoi2019模拟赛]Yuno loves sqrt technology II】

皎月半洒花

2020-04-12 22:13:27

Solution

### 正文 还是考虑二次离线,那么需要预处理 $$\sum_{i=1}^k (i,i)(1,i-1)\quad | \quad \sum_{i=1}^k (i,i)(1,i)$$ 和 $$\sum_{i=k+1}^n (i,i)(i+1,n)\quad | \quad \sum_{i=k+1}^n (i,i)(i,n)$$ 这 $4$ 个信息,因为在计算逆序对的时候是有方向性的,$l$ 向左扩展/向右收缩对应的是 $(l+1,n)$ 之间的信息,$r$ 向右扩展/左收缩对应的是 $(1,r-1)$ 之间的信息。同时注意到由于 $(i,i)(1,i)=(i,i)(1,i-1)$ ,所以本质上是两个信息。这个可以 $O(n\log n)$ 预处理。 同理,对于不能预处理的区间,也是要分左边的贡献和右边的贡献来做。根据方向,可以方便地判断每种情况贡献应该怎么加,拿两种值域分块,分别维护前缀和&后缀和就好了。 实现细节方面需要注意: 1、本题数据中存在 $a_i$ 相同的情况,这个地方会卡求逆序对时的边界,注意判一下即可。 2、值域分块需要注意,只需要修改整块的 `sum`,零散的点单独算贡献。 ____ ### 补充知识 #### 1、二次离线在莫队上的应用 大概是一种需要维护信息具有可减性的莫队。只要具可减性,就可以容斥,就可以二次离线。所谓『二次离线』,大概是指由于普通莫队无法快速计算贡献,所以第一次离线把询问离线下来,第二次离线把莫队的转移过程离线下来。然后由于信息具有可减性(比如常见的「点对数」),那么可以: 记 $(a,b)(c,d)$ 表示区间 $[a,b]$ 内的点和区间 $[c,d]$ 内的点对彼此产生的贡献(区间内部不算)。 (1)如果 $[l,r]\to [l+t,r]$ ,那么可知 $$\Delta ans=\sum_{i=l}^{l+t-1} (i,i)(i+1,r)=\sum_{i=l}^{l+t-1} \left((i,i)(1,r)-(i,i)(1,i)\right)=(l,l+t-1)(1,r)-\sum_{i=l}^{l+t-1}(i,i)(1,i)$$ (2)如果 $[l,r]\to [l-t,r]$ ,那么可知 $$\Delta ans=\sum_{i=l-t}^{l-1} (i,i)(i+1,r)=\sum_{i=l-t}^{l-1} \left((i,i)(1,r)-(i,i)(1,i)\right)=(l-t,l-1)(1,r)-\sum_{i=l-t}^{l-1}(i,i)(1,i)$$ (3)如果 $[l,r]\to [l,r+t]$ ,那么可知 $$\Delta ans=\sum_{i=r+1}^{r+t}(i,i)(l,i-1)=\sum_{i=r+1}^{r+t}((1,i-1)(i,i)-(1,l-1)(i,i))=-(1,l-1)(r+1,r+t)+\sum_{i=r+1}^{r+t}(i,i)(1,i-1)$$ (4)如果 $[l,r]\to [l,r-t]$ ,那么可知 $$\Delta ans=\sum_{i=r-t+1}^{r}(i,i)(l,i-1)=\sum_{i=r-t+1}^{r}((1,i-1)(i,i)-(1,l-1)(i,i))=-(1,l-1)(r-t+1,r)+\sum_{i=r-t+1}^{r}(i,i)(1,i-1)$$ 其中 $\sum$ 并不是真正的 $\sum$ ,不同情况下需要按顺序(即不再有交换律),比如 $[l,r]\to [l,r-t]$ 时就需要从 $r-1$ 算到 $r-t$ 。 然后这样容斥之后,后面的 $\sum$ 就可以预处理了,前面的 $()()$ ,由于莫队的复杂度,可以知道至多有 $n\sqrt m$ 个不同的询问,这样就可以把每一组询问打标记,打到左端点是 $1$ 的那个询问上 (比如 $[l,r]\to [l,r-t]$ 就打到 $l-1$ 上)。最后扫一遍全部的 $i\in[1,n]\cap\mathbb{Z_+}$,这样最终复杂度 $O(n\sqrt m)$ 。可以看出比起普通的莫队,二次离线还有一个好处,就是只有 $O(n)$ 次插入,于是对于某些题就可以用值域分块的技巧做到 $O(n\sqrt m+n\sqrt n)$ 。 #### 2、值域分块 发现本质上需要支持这么一种操作: > 维护一个值域序列,要求 $O(\sqrt n)$ 修改, $O(1)$ 求区间和。 这个比较有意思。发现要求 $O(1)$ 求区间和,那自然就是要维护前缀和。于是就分别维护块的前缀和 and 块内部的前缀和,每次修改就是要修改之后的块的前缀和 and 散点所在块内部的前缀和,查询作差即可。 最后复杂度 $O(n\sqrt m+n\sqrt n)$ 。 ```cpp using IPT::qrd; using IPT::qra; using IPT::qrs; using IPT::qrdb; using OPT::qw; using OPT::qwa; using OPT::qws; vector< tuple<int, int, int> > ql[N], qr[N] ; struct query{ int id, l, r ;} q[N] ; il bool comp(const query &a, const query &b){ return (bl[a.l] ^ bl[b.l]) ? bl[a.l] < bl[b.l] : ((bl[a.l] & 1) ? a.r < b.r : a.r > b.r) ; } il int ask(int p){ int ret = 0 ; for ( ; p ; p -= p & -p) ret += _bit[p] ; return ret ; } il void add(int p){ for ( ; p <= V ; p += p & -p) _bit[p] ++ ; } il void Inssuf(const int &x){ for (int i = blv[x] + 1 ; i <= blv[V] ; ++ i) sumb[i] ++ ; for (int i = x ; blv[x] == blv[i] && i <= V ; ++ i) sump[i] ++ ; } il int Asksuf(const int &x){ return sump[x] + sumb[blv[x]] ; } il void Inspre(const int &x){ for (int i = 1 ; i <= blv[x] - 1 ; ++ i) sumb[i] ++ ; for (int i = x ; blv[x] == blv[i] && i >= 1 ; -- i) sump[i] ++ ; } il int Askpre(const int &x){ return sump[x] + sumb[blv[x]] ; } int main(){ qrd(n), qrd(m), B = n / sqrt(m) ; for (int i = 1 ; i <= n ; ++ i) qrd(base[i]), tmp[i] = base[i] ; sort(tmp + 1, tmp + n + 1) ; L = unique(tmp + 1, tmp + n + 1) - tmp - 1 ; for (int i = 1 ; i <= n ; ++ i){ base[i] = lwb(tmp + 1, tmp + L + 1, base[i]) - tmp ; V = max(base[i], V) ; bl[i] = i / B ; } B = sqrt(V) + 1 ; for (int i = 0 ; i <= V ; ++ i) blv[i] = i / B + 1 ; for (int i = n ; i >= 1 ; -- i) s2[i] = s2[i + 1] + ask(base[i] - 1), add(base[i]) ; fill(_bit, _bit + V + 1, 0) ; for (int i = 1 ; i <= n ; ++ i) s1[i] = s1[i - 1] + i - 1 - ask(base[i]), add(base[i]) ; for (int i = 1 ; i <= m ; ++ i) qrd(q[i].l), qrd(q[i].r), q[i].id = i ; sort(q + 1, q + m + 1, comp) ; q[0].l = 1, q[0].r = 0 ; for (int i = 1 ; i <= m ; ++ i){ int newl = q[i].l ; int newr = q[i].r ; int l = q[i - 1].l ; int r = q[i - 1].r ; ans[i] -= s2[l] + s1[r] ;//l 的贡献变成了一个后缀,原来是 l'-(l-1),现在变成了 l-l' ans[i] += s2[newl] + s1[newr] ;//l 和 r 要分开计算贡献 if (newl < l) qr[newr + 1].emplace_back(newl, l - 1, -i) ; else if (newl > l) qr[newr + 1].emplace_back(l, newl - 1, i) ; if (newr < r) ql[l - 1].emplace_back(newr + 1, r, i) ; else if (newr > r) ql[l - 1].emplace_back(r + 1, newr, -i) ; } for (int i = 1 ; i <= n ; ++ i){ Inspre(base[i]) ; for (const auto &j : ql[i]){ int l, r, id ; tie(l, r, id) = j ; ll tmp = 0 ; for (int o = l ; o <= r ; ++ o) tmp += Askpre(base[o] + 1) ; if (id < 0) ans[id * (-1)] -= tmp ; else ans[id] += tmp ; } } memset(sumb, 0, sizeof(sumb)) ; memset(sump, 0, sizeof(sump)) ; for (int i = n ; i >= 1 ; -- i){ Inssuf(base[i]) ; for (const auto &j : qr[i]){ int l, r, id ; tie(l, r, id) = j ; ll tmp = 0 ; for (int o = l ; o <= r ; ++ o) tmp += Asksuf(base[o] - 1) ; if (id < 0) ans[id * (-1)] -= tmp ; else ans[id] += tmp ; } } for (int i = 1 ; i <= m ; ++ i) ans[i] += ans[i - 1] ; for (int i = 1 ; i <= m ; ++ i) res[q[i].id] = ans[i] ; qwa(res, n, '\n', '\n') ;return 0 ; } ```