把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷6578】[Ynoi2019] 魔法少女网站(操作分块+序列分块)

题目链接

  • 给定一个长度为 \(n\) 的序列 \(a_{1\sim n}\)
  • \(q\) 次操作,分为两种:将 \(a_x\) 修改为 \(y\);询问 \([l,r]\) 中有多少个子区间的最大值小于等于 \(v\)
  • \(1\le n,q\le3\times10^5\)\(1\le a_i,y\le n\)

操作分块:处理单点修改

如果没有修改操作,容易想到把询问按 \(v\) 从小到大排序,把小于等于 \(v\) 的数都标记为 \(1\),那么就相当于询问一个区间内有多少个全 \(1\) 子区间。(这一部分的具体实现会在后文提及)

而对于这种单点修改问题,一个经典的解决方案就是 操作分块

即,考虑把每 \(SQ\) 个操作看成一个操作块,则一个操作块中会被修改的元素至多只有 \(SQ\) 个,称这些元素为特殊元素。

我们按照前面的想法把询问按 \(v\) 从小到大排序,把小于等于 \(v\) 的非特殊元素都标记为 \(1\)

然后,对于每一个询问,我们处理掉在它之前的那些修改,再把小于等于 \(v\) 的特殊元素标记为 \(1\),并在询问之后撤销对特殊元素的标记。

一个操作块中每个非特殊元素只会被标记一次,一个询问最多标记 \(SQ\) 个特殊元素,因此修改次数的上界应该是 \(\frac q{SQ}\times n+q\times SQ\)。询问次数仍然是 \(q\)

实际实现中由于常数问题,\(SQ\) 要取得大一些。

序列分块

修改次数是 \(O(q\sqrt n)\),询问次数是 \(O(q)\),修改次数与询问次数不均衡,因此就要用单次修改复杂度与单次询问复杂度不均衡的分块,使得总复杂度变得均衡。

考虑维护好每段 极长 的连续的 \(1\)。对于每个块,记录开头连续的 \(1\) 的个数和结尾连续的 \(1\) 的个数,并记下中间部分的答案。

\(x\) 打上标记,相当于是将 \(x\)\(x-1\)\(x+1\) 所在段合并,讨论一下它们在块开头、块结尾还是块中间,然后更新块信息(如果这个段跨越多个块,我们只考虑它在 \(x\) 所在块的部分,因为并不会对其他块造成影响)。

而要撤销标记,只要把信息还原回去就好了。

询问的时候同时维护好最后一个极长段的长度和答案,散块直接暴力,整块就把开头连续的 \(1\) 的个数加到最后一个极长段中,如果这个块不全是 \(1\) 则在计算这个极长段的贡献之后将其更新为结尾连续的 \(1\) 的个数,并注意加上中间部分的答案。

代码:\(O(n\sqrt n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 300000
#define BS 700
#define BT 450
#define SQ 2100
#define LL long long
using namespace std;
int n,m,m1,m2,a[N+5],id[N+5],u[N+5];LL V[N+5],ans[SQ+5];
I bool cmp(CI x,CI y) {return a[x]<a[y];}
struct Q {int p,x,y,v;I bool operator < (Cn Q& o) Cn {return v<o.v;}}p[SQ+5],q[SQ+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
namespace B//序列分块
{
	int o[N+5],p[N+5],q[N+5],A[BT+5],B[BT+5];LL C[BT+5];//A[i]记录第i个块开头极长段的右端点,B[i]记录第i个块结尾极长段的左端点,C[i]记录第i个块中间段的答案
	int bl[N+5],LP[BT+5],RP[BT+5];I void Init() {for(RI i=1;i<=n;++i) !LP[bl[i]=(i-1)/BS+1]&&(LP[bl[i]]=i),RP[bl[i]]=i;}//预处理
	I void Cl() {for(RI i=0;i<=n+1;++i) o[i]=0,p[i]=i+1,q[i]=i-1;for(RI i=1;i<=bl[n];++i) A[i]=RP[i-1],B[i]=RP[i]+1,C[i]=0;}//清空
	int T,St[N+5];LL Sv[N+5];I void U(CI x,CI op=0)//标记x,op表示这个操作是否需要撤销
	{
		RI b=bl[x],f=1;op&&(St[++T]=x,Sv[T]=C[b]),o[x]=1,p[x]=p[x-1],q[x]=q[x+1],q[p[x]]=q[x],p[q[x]]=p[x];//合并块
		A[b]==x-1?(f=0,A[b]=min(q[x],RP[b])):C[b]-=V[x-p[x]],B[b]==x+1?(f=0,B[b]=max(p[x],LP[b])):C[b]-=V[q[x]-x],f&&(C[b]+=V[q[x]-p[x]+1]);//讨论块的位置
	}
	I void Bk()//撤销
	{
		RI x,b,P,Q;W(T) o[x=St[T]]=0,C[b=bl[x]]=Sv[T--],P=p[x],Q=q[x],
			q[P]=x-1,p[Q]=x+1,p[x]=x+1,q[x]=x-1,A[b]>=x&&(A[b]=x-1),B[b]<=x&&(B[b]=x+1);//还原信息
	}
	I void BF(CI l,CI r,int& v,LL& s) {for(RI i=l;i<=r;++i) o[i]?(v+=min(q[i],r)-i+1,i=q[i]):(s+=V[v],v=0);}//散块暴力
	I LL Q(CI l,CI r)//询问
	{
		RI L=bl[l],R=bl[r],v=0;LL s=0;if(L==R) return BF(l,r,v,s),s+V[v];
		BF(l,RP[L],v,s);for(RI i=L+1;i<R;++i) v+=A[i]-RP[i-1],A[i]^RP[i]&&(s+=V[v]+C[i],v=RP[i]-B[i]+1);return BF(LP[R],r,v,s),s+V[v];//处理整块
	}
}
int w[SQ+5],tmp[N+5];I void Solve()//操作分块
{
	RI i,j,k,o,t,c=0;for(i=1;i<=m1;++i) !u[p[i].x]&&(u[w[++c]=p[i].x]=a[p[i].x]);//记下特殊元素
	for(sort(q+1,q+m2+1),B::Cl(),i=j=k=1;i<=n;++i)//将询问按v排序
	{
		W(j<=n&&a[id[j]]==i) !u[id[j]]&&(B::U(id[j]),0),++j;//把等于i的数标记为1
		W(k<=m2&&q[k].v==i) {for(o=1;o<=m1&&p[o].p<q[k].p;++o) u[p[o].x]=p[o].y;//处理在询问之前的修改
			for(t=0,o=1;o<=c;++o) u[w[o]]<=i&&(B::U(w[o],1),++t),u[w[o]]=a[w[o]];ans[q[k].p]=B::Q(q[k].x,q[k].y),B::Bk(),++k;}//给特殊元素打标记,并在询问后撤销
	}
	for(i=1;i<=m1;++i) a[p[i].x]=p[i].y;for(i=1;i<=m2;++i) writeln(ans[i]);//处理所有修改;输出询问答案
	for(sort(w+1,w+c+1,cmp),i=j=1,k=0;i<=c;tmp[++k]=w[i++]) W(j<=n&&(u[id[j]]||a[id[j]]<a[w[i]])) !u[id[j]]&&(tmp[++k]=id[j]),++j;//类似归并排序,保持id有序
	W(j<=n) !u[id[j]]&&(tmp[++k]=id[j]),++j;for(i=1;i<=n;++i) id[i]=tmp[i],u[i]=0;
}
int main()
{
	RI Qt,i,op,x,y,z;for(read(n,Qt),i=1;i<=n;++i) read(a[i]),id[i]=i,V[i]=1LL*i*(i+1)>>1;B::Init(),sort(id+1,id+n+1,cmp);
	W(Qt--) read(op,x,y),op==1?p[++m1]=(Q){m2,x,y,0}:(read(z),++m2,q[m2]=(Q){m2,x,y,z}),(m1+m2==SQ||!Qt)&&(Solve(),m1=m2=0);return clear(),0;//每SQ个操作看作一块
}
posted @ 2021-09-03 09:03  TheLostWeak  阅读(167)  评论(0编辑  收藏  举报