zoukankan      html  css  js  c++  java
  • 【洛谷6578】[Ynoi2019] 魔法少女网站(操作分块+序列分块)

    题目链接

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

    操作分块:处理单点修改

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

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

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

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

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

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

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

    序列分块

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

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

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

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

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

    代码:(O(nsqrt 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('
    ');}
    }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个操作看作一块
    }
    
    败得义无反顾,弱得一无是处
  • 相关阅读:
    ORM之聚合和分组查询
    ORM之ManyToManyField操作
    ORM之ForeignKey操作
    ORM之一般操作
    ORM之元信息
    js浮点数的加减乘除
    如何用js去判断当前是否在微信中打开的链接页面
    Vue应用框架整合与实战--Vue技术生态圈篇
    图片纯前端JS压缩的实现
    js要怎么接收后端传的excel文件流?
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu6578.html
Copyright © 2011-2022 走看看