• 【LOJ2461】「2018 集训队互测 Day 1」完美的队列(分块+双指针)


    点此看题面

    大致题意: 让你维护(n)个有限定长度的队列,每次区间往队列里加数,求每次加完后的队列里剩余元素种类数。

    核心思路

    这道题可以用分块+双指针去搞。

    考虑求出每个操作插入的元素在队列中被全部弹完所需要的时间(Max_i),最后差分即可求出答案。

    我们可以(O(sqrt n))枚举块,然后(O(m))枚举询问,从而统计每一个块对每一个询问的贡献值。

    因此,我们主要要考虑的,就是分别对于整块非整块,如何求出其对于一个询问的贡献。

    整块对询问的贡献

    整块对询问的贡献应该是比较好统计的。

    考虑记录一个(tot)表示整个块被完全覆盖的次数,并开一个数组(t)表示每个队列还需要被覆盖多少次才能把当前数弹掉(初始化:(t_i=a_i))。

    然后,我们用一个变量(Mx)维护(t)的最大值,则对于一个覆盖整块的操作,它会被全部弹完,当且仅当(Mxle tot)

    由于这个被全部弹完的时间显然是递增的,因此我们可以用双指针来维护,即固定当前正在处理的操作为左端点,然后移动右端点使得(Mxle tot)

    则对于一个整块操作,若移动右端点,则我们将(tot)(1),反之减(1)

    对于一个非整块操作,若移动右端点,则我们将操作范围内的(t_i)(1),反之加(1),同时重新求一遍(Mx)

    移动完后,我们将当前左端点的(Max)值向当前右端点的位置取(max)即可。

    这样,我们就处理完了整块对询问的贡献。

    非整块对询问的贡献

    这就略微麻烦了。

    为了处理这个,我们要先在前面处理整块的时候维护一些信息:

    • (Sum_i):维护在前(i)个操作中有多少个覆盖整块的操作。
    • (Cov_i):记录第(i)个覆盖整块的操作的时间。
    • (NCov_i):记录第(i)个没有覆盖整块但与这个整块有交集的操作的时间。
    • (Pos_i):记录第(i)个没有覆盖整块的操作的上一个覆盖整块操作的时间。

    接下来,考虑枚举块内的每一个位置,然后枚举每一个非整块操作,统计贡献。

    让我们来思考一下,如何求出某一操作在什么时候会被弹完。

    实际上我们可以继续使用双指针。

    我们重新初始化一个(Mx)为当前枚举到位置的(a_i),然后统计右端点与左端点之间有多少个覆盖整块的操作(用(Sum)相减即可)以及有多少个覆盖第(i)个位置的非整块操作(双指针可以轻松维护这一信息),并用(Mx)减去这两个值。

    显然,当一次操作被弹完了,当且仅当(Mxle0)

    但注意这里用来更新左端点(Max)的值就不一定是右端点的位置了,因为有可能它是在右端点与上一个非整块操作间的某一个整块操作时弹完的,这就需要分类讨论了,具体实现详见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define SN 400
    #define max(x,y) ((x)>(y)?(x):(y))
    #define min(x,y) ((x)<(y)?(x):(y))
    #define Gmax(x,y) (x<(y)&&(x=(y)))
    using namespace std;
    int n,m,s,a[N+5],v[N+5],t[N+5],cnt[N+5],Sum[N+5],Cov[N+5],NCov[N+5],Pos[N+5];
    struct Op {int l,r,v,Mx;}o[N+5];
    struct Event
    {
    	int t,v,op;I Event(CI _t=0,CI _v=0,CI _op=0):t(_t),v(_v),op(_op){}
    	I bool operator < (Con Event& o) Con {return t<o.t;}
    }e[(N<<1)+5];
    class FastIO
    {
    	private:
    		#define FS 100000
    		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
    		#define pc(c) (C^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
    		#define tn (x<<3)+(x<<1)
    		#define D isdigit(c=tc())
    		int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
    	public:
    		I FastIO() {A=B=FI;}
    		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
    		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
    		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
    		Tp I void writeln(Con Ty& x) {write(x),pc('
    ');}
    		I void clear() {fwrite(FO,1,C,stdout),C=0;}
    }F;
    I void BlockSolve(CI l,CI r)//求解一个块对答案的贡献
    {
    	#define Cover(x) (o[x].l<=l&&r<=o[x].r)//判断第x个操作是否覆盖当前块
    	#define Intersect(x) (o[x].l<=r&&l<=o[x].r)//判断第x个操作是否与当前块有交集
    	#define ReBuild() for(Mx=0,i=l;i<=r;++i) Gmax(Mx,t[i]);//重构块,即重求Mx
    	#define Update(x,op)
    	{
    		if(Cover(x)) tot+=op;else if(Intersect(x))
    		{
    			for(i=max(l,o[x].l),lim=min(r,o[x].r);i<=lim;++i) t[i]-=op;
    			ReBuild();
    		}
    	}//加入/删除一次操作,分覆盖整块与有交集两种情况讨论
    	RI i,Mx,lim,tot=0,H=1,T=0,cc=0,ncc=0;
    	ReBuild();for(H=1;H<=m;++H)//处理整块对询问的贡献
    	{
    		Update(H,-1);W(T<=m&&Mx>tot) {++T;Update(T,1);}//移动双指针端点
    		Sum[H]=Sum[H-1],Cover(H)?(Gmax(o[H].Mx,T),++Sum[Cov[++cc]=H])//统计前缀和,对于整块处理
    		:Intersect(H)&&(NCov[++ncc]=H,Pos[ncc]=cc);//存储下有交集的块的信息
    	}
    	#undef Cover
    	#define Cover(x) (o[NCov[x]].l<=i&&i<=o[NCov[x]].r)//重新定义Cover为第x个操作是否覆盖当前枚举的位置
    	for(i=l;i<=r;++i) for(Mx=a[i],H=1,T=0;H<=ncc;++H)//枚举位置和非整块操作
    	{
    		if(Mx+=Sum[NCov[H]]-Sum[NCov[H-1]],!Cover(H)) continue;//统计Mx,如果不覆盖则跳过
    		++Mx;W(T^ncc&&Mx>0) ++T,Mx-=Sum[NCov[T]]-Sum[NCov[T-1]],Cover(T)&&--Mx;//移动双指针端点
    		Mx>0?(Sum[m]-Sum[NCov[T]]>=Mx?Gmax(o[NCov[H]].Mx,Cov[Pos[T]+Mx]):(o[NCov[H]].Mx=m+1))//分类讨论更新答案
    		:Gmax(o[NCov[H]].Mx,Cover(T)?(Mx?Cov[Pos[T]+Mx+1]:NCov[T]):Cov[Pos[T]+Mx]);
    	}
    }
    int main()
    {
    	RI i,p,ans=0;for(F.read(n,m),s=sqrt(n),i=1;i<=n;++i) F.read(a[i]),t[i]=a[i];//初始化t[i]为a[i]
    	for(i=1;i<=m;++i) F.read(o[i].l,o[i].r,o[i].v);//读入
    	for(i=1;i<=n/s;++i) BlockSolve((i-1)*s+1,i*s);n%s&&(BlockSolve(n/s*s+1,n),0);//分块处理答案
    	for(i=1;i<=m;++i) e[(i<<1)-1]=Event(i,o[i].v,1),e[i<<1]=Event(o[i].Mx,o[i].v,-1);//差分
    	for(sort(e+1,e+(m<<1)+1),p=i=1;i<=m;++i)//枚举时间
    	{
    		W(p<=(m<<1)&&e[p].t==i) !cnt[e[p].v]&&++ans,!(cnt[e[p].v]+=e[p].op)&&--ans,++p;//更新答案
    		F.writeln(ans);//输出答案
    	}return F.clear(),0;
    }
    
  • 相关阅读:
    命令拷屏之网络工具
    PHP 设计模式 笔记与总结(1)命名空间 与 类的自动载入
    Java实现 计蒜客 1251 仙岛求药
    Java实现 计蒜客 1251 仙岛求药
    Java实现 计蒜客 1251 仙岛求药
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 LeetCode 143 重排链表
    Java实现 LeetCode 143 重排链表
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/LOJ2461.html
Copyright © 2020-2023  润新知