原文链接
第二分块,神仙大分块题,不过想清楚了后应该难度相对不太大。
Part.1
先看这题的数据范围(a_ileq 5e5),想到分块的方式应该与值域有关。另外我们可以发现,我们的值是只减小不增大的,考虑值域上应该有一个均摊,对每块做一个值域,考虑我们可以在这个上面搞一些事情做一些操作,使复杂度正确。然后我就不会了
Part.2
看了出题人的题解后我们可以发现有一个很有意思的trick,我们考虑值域是不增的,所以我们可以让值域最大值与最小值的差值越来越小。
设当前修改操作有一个(x),当前最大值为(mx),我们分两种情况讨论。
- (mx le x imes 2) 此时我们把([x+1,mx])上的值暴力减。
- (mx > x imes 2)此时我们把([1,x])上的值暴力加,然后打上块整体减的(tag)。
我们每次总是使得块的最大值与最小值越来越近,所以一个块的总复杂的是(O(n))的,所有块的总复杂度是(O(nsqrt n))。
到目前为止,我们解决了最重要的操作。
Part.3
但询问时我们每个块如果只记录当前值在块内出现次数的话,很显然是不行的,因为零散块无法快速找到一个下标对应的现在的答案,所以我们考虑让值域的每个值记录她有哪些下标是当前值。或者换句话,对下标来说,就是用一个指针指向她当前在值域上的值。
我们重新定义两个操作。
- (mx le x imes 2) 此时我们把([x+1,mx])上的值所包含的下标往([1,x])合并。
- (mx > x imes 2)此时我们把([1,x])上的值所包含的下标往([x+1,x imes 2])合并,然后打上块整体减的(tag)。
如果是零散块,我们就暴力找出当前下标指向的实际的值,修改后把整个块重构。这样是(O(sqrt n))的。
什么样的数据结构能实现这样的操作呢?一个直接的想法就是链表,对每个值维护一个链表,合并是(O(1))的,满足我们的要求。
然而我们还有一个更高效的数据结构,并查集。我们对每个值维护并查集,合并是(O(1))的,但在重构块时较链表常数更小,是更优秀的选择。
Part.4
说了这么多,我们还是过不了这道题。我没有玩你们
仔细观察空间限制,发现是62.50MB,果然前面还不够dl,我们要考虑一种线性空间的做法,其实很简单,有一个trick叫逐块处理,我们发现每个块对总的时间贡献了(O(n+m))的复杂度,那么我们对每个块跑一遍所有询问就可以了。
那我们其他那么多题为什么没有用这个trick呢?因为用它要满足下面几个性质。
- 可以离线,显然我们是离线下来对每个块跑询问。
- 块之间不互相干扰。如果一个块的信息发生改变就要影响相邻或更多的块,那么就不能用。
- 答案可加性,我们是每次跑询问是把答案加入总答案,如果不可加,那么也不行。
最后告诉你一个好消息,这个题不卡常!!!所以快乐的(code)吧!其实就是细节有点多
还是贴一下我丑陋的代码吧
inline int findf(int x) {return x==fa[x]?x:fa[x]=findf(fa[x]);}
inline void merge(int u,int v)//合并两个值的下标
{
if(rt[v]) fa[rt[u]]=rt[v];
else rt[v]=rt[u],to[rt[v]]=v;
siz[v]+=siz[u];
rt[u]=siz[u]=0;
}
void build(int x)//新建块
{
mxval=tag=0;
for(int i=L[x];i<=R[x];++i)
{
mxval=max(mxval,a[i]);
if(rt[a[i]]) fa[i]=rt[a[i]];
else rt[a[i]]=fa[i]=i,to[i]=a[i];
++siz[a[i]];
}
}
void restruct(int x,int l,int r,int nx)//重构块
{
for(int i=L[x];i<=R[x];++i)
a[i]=to[findf(i)],rt[a[i]]=siz[a[i]]=0,a[i]-=tag;
for(int i=L[x];i<=R[x];++i)
to[i]=0;
l=max(l,L[x]);r=min(r,R[x]);
for(int i=l;i<=r;++i)
a[i]=a[i]-(a[i]>nx)*nx;
build(x);
}
void modify(int nx)//修改
{
if((nx<<1)<=mxval-tag)
{
for(int i=tag+1;i<=tag+nx;++i)
if(rt[i]) merge(i,i+nx);
tag+=nx;//记得修改tag
}
else
{
for(int i=tag+nx+1;i<=mxval;++i)
if(rt[i]) merge(i,i-nx);
if(tag+nx<mxval) mxval=tag+nx;//记得更新最大值
}
}
void query(int x,int i)//询问
{
int l=q[i].l,r=q[i].r,nx=q[i].x;
if(nx+tag>5e5) return;
if(l<=L[x]&&R[x]<=r) ans[i]+=siz[nx+tag];
else
{
l=max(L[x],l);r=min(R[x],r);
for(int j=l;j<=r;++j)
if(to[findf(j)]-tag==nx) ++ans[i];
}
}
int main()
{
read(n);read(m);
klen=sqrt(n);
for(int i=1;i<=n;++i)
read(a[i]);
for(int i=1;i<=m;++i)
read(q[i].opt),read(q[i].l),read(q[i].r),read(q[i].x);
blocks=(n-1)/klen+1;
for(int i=1;i<=blocks;++i)
L[i]=R[i-1]+1,R[i]=i*klen;
R[blocks]=n;
for(int i=1;i<=blocks;++i)//先枚举块,逐块处理
{
memset(rt,0,sizeof(rt));
memset(siz,0,sizeof(siz));
build(i);
for(int j=1;j<=m;++j)
{
if(L[i]>q[j].r||R[i]<q[j].l) continue;
else if(q[j].opt==1)
{
if(q[j].l<=L[i]&&R[i]<=q[j].r)
modify(q[j].x);
else restruct(i,q[j].l,q[j].r,q[j].x);
}
else query(i,j);
}
}
for(int i=1;i<=m;++i)
if(q[i].opt==2) printf("%d
",ans[i]);
return 0;
}