题解:P8530 [Ynoi2003] 博丽灵梦
卡了 117 发卡过的,必须纪念一下。
我们先来研发一个数据结构,需要支持在一个 \(n \times n\) 的二维平面上,
- \(O(1)\) 插入一个点(带点权)。
- \(O(1)\) 删除一个点。
- \(O(\sqrt n)\) 查询一个矩形内部所有点的点权和。
保证任意时刻所有未被删除的点横坐标互不相同,纵坐标互不相同。
这就是二维分块干的事情。
为了解决这个问题,我们考虑将这个 \(n \times n\) 的平面分块。我们分成四种块:\(n^{3/4} \times n^{3/4}\) 的大块,\(n^{3/4} \times n^{1/2}\) 的中块,\(n^{1/2} \times n^{3/4}\) 的中块,\(n^{1/2} \times n^{1/2}\) 的小块。注意需要保证每个大块必须由若干个完整的中块拼成,每个中块必须由若干个完整的小块拼成。
不难发现大块共有 \(O(n^{1/2})\) 个,中块有 \(O(n^{3/4})\) 个,小块有 \(O(n)\) 个。我们对每种块分别维护块内所有点的和。
对于修改,假设插入点 \((x, y)\),我们可以直接找到 \((x, y)\) 所属的大块、两种中块、小块分别是哪一个,分别修改这些块的和,显然可以做到 \(O(1)\)。
对于删除,同样找到要删除的点所属的块,然后减去删除的点的点权即可,显然也可以做到 \(O(1)\)。
对于查询,我们稍微差分一下,变成询问一个左下角矩形内部的点权之和。

容易发现,一个左下角矩形应该包含
- 红色的整块(大块):总共 \(O(\sqrt n)\) 个。
- 蓝色的整块(中块):总共 \(O(n^{1/4} \cdot n^{1/4}) = O(\sqrt n)\) 个。
- 黄色的整块(中块):同上,总共 \(O(\sqrt n)\) 个。
- 绿色的整块(小块):总共 \(O(n^{1/4} \cdot n^{1/4}) = O(\sqrt n)\) 个。
- 灰色的散块。
对于前四个部分,可以直接枚举这些块并求和。然而对于散块,我们只能枚举散块内部的单点。
但是注意到由于所有点的 \(x\) 坐标和 \(y\) 坐标都互不相同,又因为灰色区域的宽度不会超过 \(O(n^{1/2})\),因此这一部分的点数只有 \(O(n^{1/2})\) 个。我们考虑存下每个 \(x\) 对应的 \(y\),还有每个 \(y\) 对应的 \(x\)。然后我们枚举 \(x/y\) 坐标,看看当前 \(x/y\) 坐标对应的点是否在灰色部分,如果在就将这个点的点权加入答案。
这样,查询就做到了 \(O(\sqrt n)\)。
接下来看这个题:矩形带权数颜色。
我们考虑一维的数颜色怎么做。我们选取每个颜色第一次出现的位置为代表元进行统计。具体地,我们对每个位置 \(i\) 维护前驱(即前一个和 \(i\) 颜色相同的位置,不存在即为 \(0\))\(pre_i\),一个位置 \(i\) 是它的颜色在区间 \([l, r]\) 第一次出现的颜色当且仅当它上一次出现的位置不在区间内,即 \(pre_i < l\)。那么我们把 \((i, pre_i)\) 看作是平面上的点,那么询问 \([l, r]\) 时对答案有贡献的点就是 \(i \isin [l, r]\) 且 \(pre_i \isin [0, l)\) 的点,可以看作是一次矩形查询。
那么对于二维的情形,我们对 \(i\) 这一维做莫队,维护出询问区间内所有 \(p\) 构成的集合,这个时候可以看作是一维的情况。
我们且称 \(p_i\) 的一维为纵轴,那么我们就在纵轴上维护每个数字 \(x\) 的前驱 \(pre_x\),并同样可以看作是二维数点,也就是将 \((p_i, pre_{p_i})\) 看作是平面上的点,点权为 \(b_{a_i}\) 表示这个点颜色的权值。而对于一次查询 \((l_1, r_1, l_2, r_2)\),由于 \(i\) 一维要在 \([l_1, r_1]\) 内这个限制我们已经用莫队处理掉了,因此我们只需要考虑 \([l_2, r_2]\) 的限制,这部分和一维的情况是一模一样的,只需要在我们新生成的这个平面上查询 \(x \isin [l_2, r_2], y \isin [0, l_2)\) 这个矩形内部的权值之和即可。
现在问题就是如何支持加点,删点,动态维护 \(pre\)。
我们考虑对纵轴维护一个双向链表,也就是我们多维护一个 \(nxt\) 表示当前点的颜色下一次出现的位置(如果不存在则为 \(0\))。容易发现在链表上删除一个点是简单的,然而插入一个点是困难的,因为无法快速找到插入的位置,而撤销一次删除操作也是简单的,只需要用一个栈维护上一次的修改。所以我们使用回滚莫队(无增莫队),这样只需要支持删除和撤销操作,非常优秀。
每一次莫队的删除或撤销都对应着 \(O(1)\) 次加点和删点操作。接下来我们只需要解决这个问题:我们要支持 \(O(n \sqrt n)\) 次加/删点,\(O(n)\) 次矩形查询。我们知道,如果一个点存在前驱,那它的前驱对应唯一的后继。所以我们每次加入的点 \((x, y)\) 如果 \(y \not = 0\),那么 \(x\) 互不相同,\(y\) 互不相同。这个和我们上面说的二维分块可以解决的形式很像了。
所以我们对于所有 \(y \not = 0\) 的点,我们扔进一个上述的 \(O(1) - O(\sqrt n)\) 二维分块中;对于 \(y = 0\) 的点,就退化成一维情况,用一个 \(O(1) - O(\sqrt n)\) 的一维分块维护它们就可以了。
那么这道题就做完了,复杂度 \(O(n \sqrt m + m \sqrt n)\)。卡常小技巧。
代码删除了快速读入的部分。
#include<bits/stdc++.h>
// #pragma GCC target("avx")
#define endl '\n'
#define N 100006
#define M 1000006
#define cl(t) memset((t),0,sizeof(t))
using namespace std;
struct IO {} io;
using u32=unsigned;
int n,q,tp,B,bel[N],p[N],a[N],nxt[N],pre[N],tmp[N],buc[N];
u32 b[N],ans[M];
struct Ask {
int l,r,L,R,id;
} ask[M];
struct Stack_Node {
int x,pre,nxt,col;
} stk[M];
namespace ds1 {
constexpr int all=100489,B=317;
u32 s1[all+10],s2[B+10];
void clear() {cl(s1),cl(s2);}
void del(int k) {s2[(k-1)/B+1]-=s1[k],s1[k]=0;}
void add(int k,u32 val) {s1[k]+=val,s2[(k-1)/B+1]+=val;}
u32 query(int k)
{
u32 ret=0; int bel=(k-1)/B+1;
for(int i=1;i<bel;i++)ret+=s2[i];
for(int i=(bel-1)*B+1;i<=k;i++)ret+=s1[i];
return ret;
}
}
namespace ds2 {
constexpr int all=1e5,Big=7000,Small=350,Div=20;
u32 s1[all/Big+5][all/Big+5]; //xbig * ybig
u32 s2[all/Big+5][all/Small+5]; //xbig * ysmall
u32 s3[all/Small+5][all/Big+5]; //xsmall * ybig
u32 s4[all/Small+5][all/Small+5]; //xsmall * ysmall
u32 sx[N],sy[N];
int ix[N],iy[N];
inline void clear()
{
for(int i=0;i<all/Big+5;i++)
for(int j=0;j<all/Big+5;j++)s1[i][j]=0;
for(int i=0;i<all/Big+5;i++)
for(int j=0;j<all/Small+5;j++)s2[i][j]=0;
for(int i=0;i<all/Small+5;i++)
for(int j=0;j<all/Big+5;j++)s3[i][j]=0;
for(int i=0;i<all/Small+5;i++)
for(int j=0;j<all/Small+5;j++)s4[i][j]=0;
for(int i=0;i<all+10;i++)
sx[i]=sy[i]=ix[i]=iy[i]=0;
}
inline void Del(int x,int y)
{
if(!ix[x])return;
u32 val=sx[x];
int x_b=(x-1)/Big+1,y_b=(y-1)/Big+1;
int x_s=(x-1)/Small+1,y_s=(y-1)/Small+1;
s1[x_b][y_b]-=val;
s2[x_b][y_s]-=val;
s3[x_s][y_b]-=val;
s4[x_s][y_s]-=val;
sx[x]=sy[y]=ix[x]=iy[y]=0;
}
inline void add(int x,int y,u32 val)
{
sx[x]=val,sy[y]=val,ix[x]=y,iy[y]=x;
int x_b=(x-1)/Big+1,y_b=(y-1)/Big+1;
int x_s=(x-1)/Small+1,y_s=(y-1)/Small+1;
s1[x_b][y_b]+=val;
s2[x_b][y_s]+=val;
s3[x_s][y_b]+=val;
s4[x_s][y_s]+=val;
}
inline u32 query(int x,int y)
{
u32 ret=0;
int x_b=(x-1)/Big+1,y_b=(y-1)/Big+1;
int x_s=(x-1)/Small+1,y_s=(y-1)/Small+1;
int x_lb=(x_b-1)*Div+1,y_lb=(y_b-1)*Div+1;
int x_ls=(x_s-1)*Small+1,y_ls=(y_s-1)*Small+1;
for(int i=1;i<x_b;i++)
for(int j=1;j<y_b;j++)ret+=s1[i][j];
for(int i=1;i<x_b;i++)
for(int j=y_lb;j<y_s;j++)ret+=s2[i][j];
for(int i=x_lb;i<x_s;i++)
for(int j=1;j<y_b;j++)ret+=s3[i][j];
for(int i=x_lb;i<x_s;i++)
for(int j=y_lb;j<y_s;j++)ret+=s4[i][j];
for(int i=x;i>=x_ls;i--)if(ix[i]<=y)ret+=sx[i];
for(int i=y;i>=y_ls;i--)if(iy[i]<x_ls)ret+=sy[i];
return ret;
}
}
namespace ds {
inline void clear() {ds1::clear(),ds2::clear();}
inline void del(int x,int y) {if(!y)ds1::del(x); else ds2::Del(x,y);}
inline void add(int x,int y,u32 val) {if(!y)ds1::add(x,val); else ds2::add(x,y,val);}
inline u32 query(int x,int y) {return ds1::query(x)+ds2::query(x,y);}
}
inline void del(int k)
{
int x=p[k],col=a[k];
ds::del(x,pre[x]),stk[++tp]={x,pre[x],nxt[x],col};
if(nxt[x])ds::del(nxt[x],pre[nxt[x]]),pre[nxt[x]]=pre[x],ds::add(nxt[x],pre[nxt[x]],b[col]);
if(pre[x])nxt[pre[x]]=nxt[x];
pre[x]=nxt[x]=0;
}
inline void undo()
{
int x=stk[tp].x;
int pr=stk[tp].pre,nx=stk[tp].nxt,col=stk[tp].col; tp--;
if(nx)ds::del(nx,pre[nx]),pre[nx]=x,ds::add(nx,pre[nx],b[col]);
if(pr)nxt[pr]=x;
pre[x]=pr,ds::add(x,pre[x],b[col]),nxt[x]=nx;
}
main()
{
n=io.read();
for(int i=1;i<=n;i++)p[i]=io.read();
for(int i=1;i<=n;i++)a[i]=io.read();
for(int i=1;i<=n;i++)b[i]=io.read<u32>();
q=io.read(),B=n/pow(q,0.5);
for(int i=1,l,r,x,y;i<=q;i++)
l=io.read(),r=io.read(),x=io.read(),y=io.read(),ask[i]={l,r,x,y,i};
for(int i=1;i<=n;i++)bel[i]=(i-1)/B+1;
sort(ask+1,ask+1+q,[](Ask x,Ask y) {
if(bel[x.l]!=bel[y.l])return bel[x.l]<bel[y.l];
return x.r>y.r;
});
int l,r,now_l;
for(int i=1;i<=q;i++)
{
if(bel[ask[i].l]!=bel[ask[i-1].l])
{
now_l=l=(bel[ask[i].l]-1)*B+1,r=n,tp=0;
ds::clear(),cl(tmp),cl(pre),cl(nxt),cl(buc);
for(int i=l;i<=r;i++)tmp[p[i]]=a[i];
for(int i=1;i<=n;i++)
if(tmp[i])pre[i]=buc[tmp[i]],buc[tmp[i]]=i,ds::add(i,pre[i],b[tmp[i]]);
for(int i=1;i<=n;i++)if(pre[i])nxt[pre[i]]=i;
}
for(;r>ask[i].r;del(r--));
for(;l<ask[i].l;del(l++));
ans[ask[i].id]=ds::query(ask[i].R,ask[i].L-1)-
ds::query(ask[i].L-1,ask[i].L-1);
for(;l>now_l;undo(),l--);
}
for(int i=1;i<=q;i++)io.writeln<u32>(ans[i]);
return 0;
}

浙公网安备 33010602011771号