CF848C Goodbye Souvenir

xfrvq

2022-05-04 13:17:23

Solution

[$\tt Link$](/problem/CF848C) ## 前言 题解清一色的 $\tt CDQ$,树套树,但是有个 $\mathcal O(n^{\frac 53}\log n)$ 是什么鬼? 这是一个分块做法,复杂度 $\mathcal O(n\sqrt n)$,但是非常阴间,建议谨慎尝试。 如果你真心想写这个做法,建议写完这题就去写 [$\tt rdiq$](/problem/P7448),[$\tt rcn$](/problem/P7721)。 ## 转化 考虑记下每个位置前一个与它颜色相同的位置 $P_i$。 那么我们可以把这个贡献从每种颜色转化到每个位置。 方法是定义每个位置的贡献为 $i-P_i$,当然要 $P_i\ge$ 询问左端点。 假如一个颜色所有出现的位置为 $b_1,\cdots,b_k$,那么总贡献为: $$ \begin{aligned} Sum&=(b_k-P_{b_k})+\cdots+(b_2-P_{b_2})\\ &=(b_k-b_{k-1})+\cdots+(b_2-b_1)\\ &=b_k-b_1 \end{aligned} $$ 可以看出最后就是我们想求的。 转化完后,我们把这个问题换成一个二维数点的形式,即有 $n$ 个点 $(i,P_i)$,其权值为 $i-P_i$。询问就相当于求 $[l,r][l,n]$ 这一段的和。 **关于点的性质:所有点的 $x$ 坐标互不相同,$y$ 坐标除了可能有多个 $0$ 之外,互不相同.** ## 修改操作 很平凡。你维护每个点出现位置的 `set`,然后就可以 $O(\log n)$ 计算每个位置的上一个/下一个颜色相同位置。 ## 二维分块 这个题用这玩意确实大材小用了,但是还是想说说。 **注意:下面介绍的是一种 $O(\sqrt n)$ 前缀矩形加 $O(1)$ 单点查询的数据结构。转化一下可以得到适合原问题的单点加矩形查** 举个例子,假设我们要加的是这么一个矩形(左下角是原点,右上角是询问点)。 ![](https://cdn.luogu.com.cn/upload/image_hosting/76po4cy3.png) 为了方便,以下记 $\mathbf a=n^{0.25},\mathbf b=n^{0.5},\mathbf c=n^{0.75}$。 首先因为整块复杂度要保证,所以块数是 $O(\sqrt n)$ 的,于是你就应该用 $\bf c\times c$ 来分块,这样总块数就是 $O(\mathbf{a\times a}=\sqrt n)$ 的。 ![](https://cdn.luogu.com.cn/upload/image_hosting/04pkufgz.png) 但是我们整块复杂度保证,散块又不行了。于是考虑在现在的灰色部分分块用来辅助原来的散块,分出 + $\bf a\times b$ 个 $\bf c\times b$ 的块 + $\bf b\times a$ 个 $\bf b\times c$ 的块 + $\bf b\times b$ 个 $\bf b\times b$ 的块 ![](https://cdn.luogu.com.cn/upload/image_hosting/d5tnp5jb.png) 现在总共分了 $4$ 种块,对于每种块我们都维护块和。 现在来算一下复杂度。 + 对于红色块而言,它一定不超过 $\sqrt n$ 个。 + 对于橙色块而言,它一行最多只有 $n\div\mathbf c=n^{0.25}$ 个,一列最多只有 $\mathbf{c\div b}=n^{0.25}$ 个,乘起来不超过 $\sqrt n$ 个。 + 对于蓝色块而言,它一行最多只有 $\mathbf{c\div b}=n^{0.25}$ 个,一列最多只有 $n\div\mathbf c=n^{0.25}$ 个,乘起来不超过 $\sqrt n$ 个。 + 对于绿色块而言,它一行/一列最多只有 $\mathbf{c\div b}=n^{0.25}$ 个,乘起来也不超过 $\sqrt n$ 个。 + 散块需要结合这题的特殊性质才可做,这里不作讨论,下面讲。 那散块如何做呢? 首先,散块本身的定义是:**一个询问覆盖了一个点但没有完全覆盖这个点所在的 $\bf b\times b$ 块**,就是上图中的黄色部分。 上文所说的性质,就是说 $x,y$ 坐标都互不相同(如果 $y=0$,这时我们再写一个一维分块做就好了。) **基于性质**,我们把散块拆分为三部分。 ![](https://cdn.luogu.com.cn/upload/image_hosting/ntxsmopv.png) 对于紫色部分,枚举紫色范围内 $x$ 坐标,然后查看 $y$ 坐标是否也在紫色范围内,如果是就加上贡献。 对于粉色部分,枚举粉色范围内 $y$ 坐标,然后查看 $x$ 坐标是否也在粉色范围内,如果是就加上贡献。 你可以在紫色部分的时候算上蓝色部分,也可以在粉色部分的时候算上蓝色部分,不要算重就行了。 ## Code ```cpp #include<stdio.h> #include<math.h> #include<string.h> #include<set> #define rep(i,a,b) for(int i = (a);i <= (b);++i) #define Rep(i,a,b) for(int i = (a);i >= (b);--i) #define clr(M) memset(M,0,sizeof M) const int N = 1e5 + 5; typedef long long ll; int n,m,a[N],p[N],q[N]; namespace blk2d{ const int B1 = 18,B2 = 324,B3 = 5832; const int S1 = 25,S2 = 330,S3 = 5840; int st2[S2],bl2[N],st3[S1],bl3[N]; ll c0[N],s0[S2]; ll c1[S1][S1],c2[S2][S1],c3[S1][S2],c4[S2][S2],c5[N],c6[N]; #define F1(i,z) rep(i,1,bl3[z] - 1) #define F2(i,z) rep(i,st3[bl3[z]],bl2[z] - 1) #define F3(i,z) rep(i,st2[bl2[z]],z) #define bl(i,t) (i - 1) / t + 1 void init(){ rep(i,1,n) bl2[i] = bl(i,B2),bl3[i] = bl(i,B3); Rep(i,n,1) st2[bl2[i]] = i,st3[bl3[i]] = bl2[i]; } void upd(int x,int y,ll t){ p[x] = y; q[y] = x; if(y == 0) return c0[x] += t,void(s0[bl2[x]] += t); int a = bl3[x],b = bl2[x],c = bl3[y],d = bl2[y]; c1[a][c] += t; c2[b][c] += t; c3[a][d] += t; c4[b][d] += t; c5[x] += t; c6[y] += t; } ll qry(int x,int y){ ll sum = 0; F1(i,x) F1(j,y) sum += c1[i][j]; F2(i,x) F1(j,y) sum += c2[i][j]; F1(i,x) F2(j,y) sum += c3[i][j]; F2(i,x) F2(j,y) sum += c4[i][j]; F3(i,x) if(p[i] <= y) sum += c5[i]; F3(i,y) if(q[i] < st2[bl2[x]]) sum += c6[i]; rep(i,1,bl2[x] - 1) sum += s0[i]; rep(i,st2[bl2[x]],x) sum += c0[i]; return sum; } } using namespace blk2d; std::set<int> P[N]; int pre(int x){ auto i = P[a[x]].lower_bound(x); return i == begin(P[a[x]]) ? 0 : *--i; } int nxt(int x){ auto i = P[a[x]].upper_bound(x); return i == end(P[a[x]]) ? 0 : *i; } void cpre(int u){ if(!u) return; int v = pre(u); upd(u,p[u],p[u] - u); upd(u,v,u - v); } int main(){ scanf("%d%d",&n,&m); rep(i,1,n) scanf("%d",a + i),P[a[i]].insert(i); init(); rep(u,1,n) p[u] = pre(u),upd(u,p[u],u - p[u]); while(m--){ int op,x,y; scanf("%d%d%d",&op,&x,&y); if(op == 1){ int w = nxt(x); P[a[x]].erase(x); P[a[x] = y].insert(x); cpre(x); cpre(w); int v = nxt(x); cpre(v); q[p[x]] = x; q[p[w]] = w; q[p[v]] = v; } else printf("%lld\n",qry(y,n) - qry(y,x - 1) - qry(x - 1,n) + qry(x - 1,x - 1)); } return 0; } ```