「学习笔记」分块与莫队

「学习笔记」分块与莫队

点击查看目录

分块

大致思想

众所周知,线段树是把一个区间分为两个区间维护,再不断往下分,直到分到单个点。

虽然它效率高,但很难处理一些特殊问题(如求区间众数)。

那有没有更方便操作而又高效的数据结构呢?考虑一种东西叫「分块」。

分块是直接把长为 \(n\) 的序列划分成 \(\sqrt{n}\) 份,每份长为 \(\sqrt{n}\)

更新或修改时,先对整块进行整体处理,再对构不成整块的点单独处理。

这样的话,分块的预处理时间复杂度为 \(\Theta(n)\),查询/修改复杂度为 \(\Theta(n\sqrt{n})\)

例题

既然是树状数组模板题,那肯定不能用树状数组做!

代码

点击查看代码
const int N=5e5+10,SN=1000,inf=1<<30;
ll T,n,a[N];
class Block{
 public:
    ll sqn,ka[N];
    class BLOCK{
     public:
        ll val,l,r;
    }bl[SN];
    inline void init(ll n){
        sqn=sqrt(n);
        _for(i,1,sqn){
            bl[i].l=bl[i-1].r+1;
            bl[i].r=(i==sqn)?n:bl[i].l+sqn;
            bl[i].val=0;
            _for(j,bl[i].l,bl[i].r){
                ka[j]=i;
                bl[i].val+=a[j];
            }
        }
    }
    inline void UpdateP(ll p,ll x){
        a[p]+=x;
        bl[ka[p]].val+=x;
    }
    inline ll Query(ll l,ll r){
        ll sum=0;
        while(bl[ka[l]].l!=l&&l<=r){
            sum+=a[l];
            ++l;
        }
        while(l+sqn-1<=r){
            sum+=bl[ka[l]].val;
            l+=sqn;
        }
        while(l<=r){
            sum+=a[l];
            ++l;
        }
        return sum;
    }
}bl;

莫队

普通莫队

一种离线算法,可以用 \(O(n\sqrt{q}+q)\) 的时间解决所有询问。

首先对所有查询的左端点 \(l\) 进行分块(把左端点在区间 \([k\sqrt{n}+1,(k+1)\sqrt{n}]\) 的分成一块),然后进行排序:对于块相同的,对右端点排序;块不同的,对左端点排序。

排序完之后就可以解决查询了。第一个查询我们直接暴力解决,之后的每个查询我们都通过移动上一个询问的左右端点来解决。

这不是 $n^2$ 的吗?为啥是复杂度是对的?

这就是排序的作用了。

考虑设排序时的块长为 \(B\)

排序之后相邻询问的左端点变化在 \(B\) 内,全部跑完复杂度 \(O(qB)\)

然后相同块内右端点是递增的,所以最坏也只需要算 \(n\) 次,全部跑完复杂度 \(O(\dfrac{n^2}{B})\)

平衡后 \(B = \dfrac{n}{\sqrt{q}}\) 最优,带回去得到时间复杂度为 \(O(n\sqrt{q}+q)\)

一个优化:奇偶性排序

仔细想想可以发现,每个块内的询问处理完了跳到下一个块时,总是会先用一些多余的时间跑回最左边再开始往右跑。

但实际上在往左跑时我们就可以处理掉询问。

所以我们在排序时判断一下块的奇偶性,奇块内部对右端点升序排序,偶块内部对右端点降序排序。

例题

思路

答案显然是:

\[\frac{\sum_{i=1}^{n}\left(\dbinom{cnt_i}{2}-\dbinom{cnt_i-1}{2}\right)}{\dbinom{r-l+1}{2}} \]

其中 \(cnt_i\) 表示颜色 \(i\) 在区间中出现的次数。

化简一下分子:

\[\begin{aligned} &\sum_{i=1}^{n}\left(\dbinom{cnt_i}{2}-\dbinom{cnt_i-1}{2}\right)\\ =&\sum_{i=1}^{n}\left(\frac{cnt_i(cnt_i-1)}{2}-\frac{(cnt_i-1)(cnt_i-2)}{2}\right)\\ =&\sum_{i=1}^{n}\frac{(cnt_i-1)(cnt_i-(cnt_i-2))}{2}\\ =&\sum_{i=1}^{n}\frac{2(cnt_i-1)}{2}\\ =&\sum_{i=1}^{n}cnt_i-1\\ \end{aligned} \]

然后直接用莫队算,每次弹出/加入一个数时更新 \(cnt_i\),顺便更新一下总和。

代码
点击查看代码
const ll N=5e4+10,P=998244353;
ll n,m,sn,c[N],ans[N][2];
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
class MODUI{
public:
	ll cnt[N],sum;
	class Q{
	public:
		ll l,r,id,k;
		inline void Join(ll le,ll rr,ll iid){
			l=le,r=rr,id=iid;
			k=(le-1)/sn+1;
			return;
		}
		bool operator<(const Q &p)const{
			if(k!=p.k)return l<p.l;
			if(k&1)return r<p.r;
			return r>p.r;
		}
	}q[N];
	inline void Join(ll le,ll rr,ll iid){q[iid].Join(le,rr,iid);}
	inline void Add(ll i){sum+=cnt[c[i]]++;}
	inline void Del(ll i){sum-=--cnt[c[i]];}
	inline void Solve(){
		sort(q+1,q+m+1);
		ll l=1,r=0;
		_for(i,1,m){
			if(q[i].l==q[i].r){
				ans[q[i].id][0]=0;
				ans[q[i].id][1]=1;
				continue;
			}
			while(l>q[i].l)Add(--l);
			while(r<q[i].r)Add(++r);
			while(l<q[i].l)Del(l++);
			while(r>q[i].r)Del(r--);
			ans[q[i].id][0]=sum;
			ans[q[i].id][1]=(r-l+1)*(r-l)/2;
		}
	}
}md;
namespace SOLVE{
	inline ll rnt() {
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=x*10+(c^48),c=getchar();
		return x*w;
	}
	inline void In(){
		n=rnt(),m=rnt();
		sn=sqrt(n);
		_for(i,1,n)c[i]=rnt();
		_for(i,1,m){
			ll l=rnt(),r=rnt();
			md.Join(l,r,i);
		}
		md.Solve();
		_for(i,1,m){
			if(!ans[i][0])puts("0/1");
			else{
				ll g=gcd(ans[i][0],ans[i][1]);
				ans[i][0]/=g,ans[i][1]/=g;
				printf("%lld/%lld\n",ans[i][0],ans[i][1]);
			}
		}
	}
}

带修莫队

思路

普通莫队是不能进行修改的,我们怎样才能让它支持修改呢?

在普通莫队中的查询区间 \([l,r]\) 我们可以看成二位坐标系上的点,那么在带修莫队中我们再加上一维:时间,即这次查询是在几次修改之后,我们可以用类似 \(\Theta(1)\) 移动左右端点的方法移动时间这一维。

简单来说,我们存一下每个修改的修改位置上修改前和修改后的值。解决查询时如果当前修改次数少了就暴力加入少了的修改,多了就暴力还原回去。

设块长为 \(s\),则每次左右端点最多移动 \(s\) 次,总复杂度 \(\Theta(n\cdot s)\)

块数为 \(q=\tfrac{n}{s}\),每次左右端点所在块改变时,时间移动 \(n\) 次,否则时间单调递增,总共移动 \(n\) 次。左端点有 \(q\) 种,右端点有 \(q\) 种,总复杂度 \(\Theta(n\cdot q^2)\)

则最优 \(s=n^{\tfrac{2}{3}}\),总复杂度 \(\Theta(n^{\tfrac{5}{3}})\)

例题

思路

就是个板子,没什么说的(

注意一些细节就可以了。

代码
点击查看代码
const ll N=2e5+10,P=998244353,inf=1<<30;
ll n,m,sn,a[N],b[N],cq,cx,ans[N];
class MODUI{
public:
	ll jl[N*5],num;
	class Q{
	public:
		ll x,y,id,k1,k2,lx;
		inline void Join(ll xx,ll yy,ll idd,ll lxx){x=xx,y=yy,id=idd,k1=(x-1)/sn+1,k2=(y-1)/sn+1,lx=lxx;}
		inline bool operator<(const Q&p){
			if(k1==p.k1){
				if(k2==p.k2)return id<p.id;
				return y<p.y;
			}
			return x<p.x;
		}
	}q[N];
	class X{
	public:
		ll x,va1,va2,id;
		inline void Join(ll xx,ll va,ll idd){id=idd,x=xx,va1=b[xx],va2=b[xx]=va;}
	}x[N];
	inline void Join(char op,ll xx,ll y,ll id){
		if(op=='Q')q[++cq].Join(xx,y,id,cx);
		else x[++cx].Join(xx,y,id);
	}
	inline void AddP(ll i){if(!jl[i])++num;++jl[i];}
	inline void DelP(ll i){--jl[i];if(!jl[i])--num;}
	inline void AddX(ll i,ll l,ll r){
		if(l<=x[i].x&&x[i].x<=r)DelP(a[x[i].x]),AddP(x[i].va2);
		a[x[i].x]=x[i].va2;
	}
	inline void DelX(ll i,ll l,ll r){
		if(l<=x[i].x&&x[i].x<=r)DelP(a[x[i].x]),AddP(x[i].va1);
		a[x[i].x]=x[i].va1;
	}
	inline void Solve(){
		sort(q+1,q+cq+1);
		ll l=1,r=0,xu=0;
		_for(i,1,cq){
			while(l>q[i].x)AddP(a[--l]);
			while(r<q[i].y)AddP(a[++r]);
			while(l<q[i].x)DelP(a[l++]);
			while(r>q[i].y)DelP(a[r--]);
			while(xu<q[i].lx)AddX(++xu,q[i].x,q[i].y);
			while(xu>q[i].lx)DelX(xu--,q[i].x,q[i].y);
			ans[q[i].id]=num;
		}
		return;
	}
}md;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline char rch(){
		char c=getchar();
		while(c<'A'||c>'Z')c=getchar();
		return c;
	}
	inline void wnt(ll x,char c){
		static int st[35];int top=0;
		do{st[top++]=x%10;x/=10;}while(x);
		while(top)putchar(st[--top]+'0');
		putchar(c);
		return;
	}
	inline void In(){
		n=rnt(),m=rnt(),sn=pow(n,2.0/3.0);
		_for(i,1,n)b[i]=a[i]=rnt();
		_for(i,1,m){
			char op=rch();ll x=rnt(),y=rnt();
			md.Join(op,x,y,i);
			if(op=='R')ans[i]=-1;
		}
		md.Solve();
		_for(i,1,m)if(ans[i]!=-1)wnt(ans[i],'\n');
		return;
	}
}

练习题

[HNOI2010]弹飞绵羊

思路

暴力的思路就是直接存弹几步弹飞,显然会被卡 T。

考虑如何优化。

我们不存弹几步弹飞,而是弹几步会弹出这个块

更新时,更新的值只与本块内的蹦床弹几步会弹出这个块有关系,所以直接暴力重构该块即可,时间复杂度为 \(\Theta(\sqrt{n})\)

查询时,实际上最多只会弹 \(\sqrt{n}\) 次,所以时间复杂度为 \(\Theta(\sqrt{n})\)

代码

点击查看代码
const int N=2e5+10,SN=1000,inf=1<<30;
ll n,m,a[N];
class Block{
public:
    ll sqn=0,ka[N];
    class BLOCK{
    public:
        ll l,r;
    }bl[SN];
    class TAN{
    public:
        ll cnt=1,nxt;
    }t[N];
    inline void init(ll n){
        sqn=sqrt(n);
        _for(i,1,sqn){
            bl[i].l=bl[i-1].r+1;
            bl[i].r=i==sqn?n:bl[i].l+sqn;
            for_(j,bl[i].r,bl[i].l){
                ka[j]=i;
                t[j].nxt=j+a[j];
                t[j].cnt=1;
                while(t[j].nxt<=bl[i].r){
					t[j].cnt=t[t[j].nxt].cnt+1;
                    t[j].nxt=t[t[j].nxt].nxt;
                }
            }
        }
        return;
    }
    inline void UpdateP(ll p,ll x){
        a[p]=x;
        for_(j,bl[ka[p]].r,bl[ka[p]].l){
            t[j].nxt=j+a[j];
            t[j].cnt=1;
            while(t[j].nxt<=bl[ka[p]].r){
				t[j].cnt=t[t[j].nxt].cnt+1;
                t[j].nxt=t[t[j].nxt].nxt;
            }
        }
    }
    inline ll Query(ll p){
        ll sum=0;
        while(p<=n){
            sum+=t[p].cnt;
            p=t[p].nxt;
        }
        return sum;
    }
}bl;

[Violet]蒲公英

思路

区间众数问题。

此时如果维护每个块内每个数的数量,直接合并会 TLE。

那么我们就预处理出块 \(i\) 到块 \(j\) 每个数的数量和众数 \(bl_{i,j}\),众数只可能是 整块本身的众数 或是 有散块后才变成众数的数,那么我们把所有散块放到整块里并更新众数即可。

这样不会 TLE 了,但会 MLE。

那如何不用占空间的的存储方式来快速求出每个数的数量?

开一个 vector 存每个数出现的位置,来辅助找出每个数在每个块第一次/最后一次出现是在整个数列中第几次出现,两者之差加一就是这个块中这个数的数量(需要二分)。

这样我们只需储存 \(bl_{i,j}\),对于散块,我们找到每个数在区间中第一次/最后一次出现是在整个数列中第几次出现,两者做差再加一即为它们的数量(此时不用二分)。

预处理 \(\Theta(n^2)\)(准确说应该是:设 \(s=块数\),时间复杂度为 \(\Theta(s^2n)\),因此可以通过调整块长来降低复杂度,卡过此题),单次查询 \(\Theta(\sqrt{n})\)

代码

点击查看代码
const int N=4e4+10,SN=250,inf=1<<30;
ll n,m,len,a[N],b[N],c[N],rk[N],la[N],ans;
vector<ll>wz[N];
namespace LISAN{
	ll ls[N];
	inline void LiSan(){
		_for(i,1,n)ls[i]=a[i];
		sort(ls+1,ls+n+1);
		len=unique(ls+1,ls+n+1)-ls-1;
		_for(i,1,n){
			b[i]=lowb(ls,len,a[i]);
			c[b[i]]=a[i];
		}
		return;
	}
}
class Block{
public:
	ll sn,ka[N],wl[SN][N],wr[SN][N];
	class BL{
	public:
		ll l,r;
		ll mx,mw;
	}bl[SN][SN];
	#define l(ii,jj) bl[ii][jj].l
	#define r(ii,jj) bl[ii][jj].r
	#define mx(ii,jj) bl[ii][jj].mx
	#define mw(ii,jj) bl[ii][jj].mw
	inline void Init(){
		sn=sqrt(n);
		ka[n+1]=sn+1;
		_for(i,1,sn){
			l(i,i)=r(i-1,i-1)+1;
			r(i,i)=(i==sn)?n:l(i,i)+sn-1;
			_for(j,1,n){
				wl[i][j]=lower_bound(wz[j].begin(),wz[j].end(),l(i,i))-wz[j].begin();
				wr[i][j]=lower_bound(wz[j].begin(),wz[j].end(),r(i,i)+1)-wz[j].begin();
			}
			_for(j,l(i,i),r(i,i)){
				ka[j]=i,la[j]=-1;
				ll cnt=wr[i][b[j]]-wl[i][b[j]];
				if(cnt>mx(i,i)||(cnt==mx(i,i)&&b[j]<mw(i,i)))
					mx(i,i)=cnt,mw(i,i)=b[j];
			}
		}
		_for(i,1,sn){
			_for(j,i+1,sn){
				l(i,j)=l(i,i);
				r(i,j)=r(j,j);
				_for(k,1,len){
					ll cnt=wr[j][k]-wl[i][k];
					if(cnt>mx(i,j)||(cnt==mx(i,j)&&k<mw(i,j)))
						mx(i,j)=cnt,mw(i,j)=k;
				}
			}
		}
		return;
	}
	inline ll Query(ll lf,ll rr){
		ll k1=min(ka[lf-1]+1,sn),k2=ka[rr+1]-1;
		if(ka[lf]!=ka[rr]&&k1<=k2){
			ll le=l(k1,k1);
			ll ri=r(k2,k2);
			BL qwq=bl[k1][k2];
			_for(i,lf,le-1){
				if(la[b[i]]==-1){
					la[b[i]]=rk[i];
					ll cnt=wr[k2][b[i]]-rk[i];
					if(cnt>qwq.mx||(cnt==qwq.mx&&b[i]<qwq.mw))
						qwq.mx=cnt,qwq.mw=b[i];
				}
			}
			_for(i,ri+1,rr){
				ll cnt=rk[i]-(la[b[i]]!=-1?la[b[i]]:wl[k1][b[i]])+1;
				if(cnt>qwq.mx||(cnt==qwq.mx&&b[i]<qwq.mw))
					qwq.mx=cnt,qwq.mw=b[i];
			}
			_for(i,lf,le-1)if(la[b[i]]!=-1)la[b[i]]=-1;
			return c[qwq.mw];
		}
		else{
			ll mx=0,mw=0;
			_for(i,lf,rr){
				if(la[b[i]]==-1)la[b[i]]=rk[i];
				ll cnt=rk[i]-la[b[i]]+1;
				if(cnt>mx||(cnt==mx&&b[i]<mw))
					mx=cnt,mw=b[i];
			}
			_for(i,lf,rr)if(la[b[i]]!=-1)la[b[i]]=-1;
			return c[mw];
		}
	}
}bl;

教主的魔法

思路

《关于我忘了调用初始化函数于是交了二十多遍这件事》

为了方便计数,我们新开一个数组,用来存对每个块内部进行排序后的结果,复杂度 \(\Theta(\sqrt{n}\sqrt{n}\log_2\sqrt{n})=O(n\log_2{\sqrt{n}})\)

每次更改,对于整块直接打 \(tag\),对于散块直接更改原数组并重新排一遍序,复杂度 \(\Theta(\sqrt{n}\log_2{\sqrt{n}})\)

每次查询,散块直接暴力算答案,整块用 lower_bound() 去算,注意要将查询值 \(c\) 减去该块 \(tag\) 以达到区间加的效果,复杂度依旧是 \(\Theta(\sqrt{n}\log_2{\sqrt{n}})\)

代码

点击查看代码
const int N=1e6+1,SN=1001;
int n,q,a[N],ans;
class Block{
public:
	int sn,b[N],ka[N];
	class BL{
	public:
		int l,r;
		int tag;
	}bl[SN];
	inline void ReSort(int kuai){
		_for(i,bl[kuai].l,bl[kuai].r)b[i]=a[i];
		sort(b+bl[kuai].l,b+bl[kuai].r+1);
	}
	inline void Init(){
		sn=sqrt(n);
		_for(i,1,sn){
			bl[i].l=bl[i-1].r+1;
			bl[i].r=(i==sn)?n:bl[i].l+sn-1;
			_for(j,bl[i].l,bl[i].r)ka[j]=i;
			ReSort(i);
		}
		return;
	}
	inline void Update(int l,int r,int val){
		int k1=ka[l],k2=ka[r];
		if(k1==k2){
			_for(i,l,r)a[i]+=val;
			ReSort(k1);
		}
		else{
			_for(i,l,bl[k1].r)a[i]+=val;
			ReSort(k1);
			_for(i,bl[k2].l,r)a[i]+=val;
			ReSort(k1);
			_for(i,k1+1,k2-1)bl[i].tag+=val;
		}
		return;
	}
	inline int Query(int l,int r,int val){
		int k1=ka[l],k2=ka[r],ans=0;
		if(k1==k2)
			_for(i,l,r)ans+=(a[i]>=val-bl[k1].tag);
		else{
			_for(i,l,bl[k1].r)ans+=(a[i]>=val-bl[k1].tag);
			_for(i,bl[k2].l,r)ans+=(a[i]>=val-bl[k2].tag);
			_for(i,k1+1,k2-1){
				int w=lower_bound(b+bl[i].l,b+bl[i].r+1,val-bl[i].tag)-b;
				ans+=bl[i].r-w+1;
			}
		}
		return ans;
	}
}bl;

颜色

思路

用类似于蒲公英那道题的方法,存储第 \(l\sim r\) 块每种颜色的数量和每种颜色的数量的平方的前缀和,散块直接加到整块里算贡献即可。

算贡献的方法:当一种颜色的数量 \(cnt_i\) 加一后,就会对答案增加 \(2cnt_i-1\) 的贡献。

设块数为 \(sn\),则预处理复杂度为 \(\Theta(sn^2m)\),全部查询复杂度为 \(\Theta(q\cdot\frac{n}{sn})\),则最优 \(sn=n^{\tfrac{1}{3}}\),设为 \(50\) 即可。

空间复杂度是 \(\Theta(sn^2m)\)所以说上边的你都不用算你直接像我一样看能不能开下就行了。

(感觉存储第 \(l\sim r\) 块的东西算是分块题的一种经典套路了吧。)

代码

点击查看代码
const ll N=5e4+10,M=2e4+10,SN=60,P=998244353;
ll n,m,q,c[N],cnt[M],f[M],ans;
class BLOCK{
public:
	ll sz,sn=50,ka[N],num[SN][SN][M],an[SN][SN][M];
	class BL{public:ll l,r;}bl[SN];
	inline void Pre(){
		sz=n/sn;
		_for(i,1,sn){
			bl[i].l=bl[i-1].r+1;
			bl[i].r=(i==sn)?n:bl[i].l+sz-1;
			_for(j,bl[i].l,bl[i].r){
				ka[j]=i;
				++num[i][i][c[j]];
			}
			_for(j,1,m)an[i][i][j]=an[i][i][j-1]+num[i][i][j]*num[i][i][j];
		}
		_for(i,1,sn)_for(j,i+1,sn)_for(k,1,m){
			num[i][j][k]=num[i][j-1][k]+num[j][j][k];
			an[i][j][k]=an[i][j][k-1]+num[i][j][k]*num[i][j][k];
		}
		return;
	}
	inline ll Query(ll l,ll r,ll a,ll b){
		ll k1=ka[l],k2=ka[r],ans=0;
		if(k1==k2){
			_for(i,l,r)cnt[c[i]]=0;
			_for(i,l,r){
				if(c[i]<a||c[i]>b)continue;
				ans+=(cnt[c[i]]<<1|1);
				++cnt[c[i]];
			}
		}
        else{
        	ans=an[k1+1][k2-1][b]-an[k1+1][k2-1][a-1];
        	_for(i,l,bl[k1].r)f[c[i]]=cnt[c[i]]=0;
        	_for(i,bl[k2].l,r)f[c[i]]=cnt[c[i]]=0;
        	_for(i,l,bl[k1].r){
				if(c[i]<a||c[i]>b)continue;
				if(!cnt[c[i]])cnt[c[i]]=num[k1+1][k2-1][c[i]];
				ans+=(cnt[c[i]]<<1|1);
				++cnt[c[i]];
			}
        	_for(i,bl[k2].l,r){
				if(c[i]<a||c[i]>b)continue;
				if(!cnt[c[i]])cnt[c[i]]=num[k1+1][k2-1][c[i]];
				ans+=(cnt[c[i]]<<1|1);
				++cnt[c[i]];
			}
        }
        return ans;
	}
}bl;
namespace SOLVE{
	inline ll rnt() {
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=x*10+(c^48),c=getchar();
		return x*w;
	}
	inline void wnt(ll x){
		static int st[35];int top=0;
		do{st[top++]=x%10;x/=10;}while(x);
		while(top)putchar(st[--top]+'0');
		return;
	}
	inline void In(){
		n=rnt(),m=rnt(),q=rnt();
		_for(i,1,n)c[i]=rnt();
		bl.Pre();
		_for(i,1,q){
			ll l=rnt()^ans,r=rnt()^ans,a=rnt()^ans,b=rnt()^ans;
			wnt(ans=bl.Query(l,r,a,b)),puts("");
		}
		return;
	}
}

小B的询问

思路

直接套莫队板子即可。

代码

点击查看代码
const ll N=5e4+10,P=998244353;
ll n,m,k,sn,a[N],ans[N];
class MODIU{
public:
	ll sum,c[N];
	class Q{
		public:
		ll l,r,id,k;
		inline void Join(ll le,ll ri,ll i){l=le,r=ri,id=i,k=(le-1)/sn+1;return;}
		inline bool operator<(Q p){
			if(k!=p.k)return l<p.l;
			if(k&1)return r<p.r;
			return r>p.r;
		}
	}q[N];
	inline void Join(ll le,ll ri,ll i){q[i].Join(le,ri,i);return;}
	inline void Add(ll i){sum-=c[i]*c[i],++c[i],sum+=c[i]*c[i];}
	inline void Del(ll i){sum-=c[i]*c[i],--c[i],sum+=c[i]*c[i];}
	inline void Solve(){
		sort(q+1,q+m+1);
		ll l=1,r=0;
		_for(i,1,m){
			while(l>q[i].l)Add(a[--l]);
			while(r<q[i].r)Add(a[++r]);
			while(l<q[i].l)Del(a[l++]);
			while(r>q[i].r)Del(a[r--]);
			ans[q[i].id]=sum;
		}
	}
}md;
namespace SOLVE{
	inline ll rnt() {
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=x*10+(c^48),c=getchar();
		return x*w;
	}
	inline void wnt(ll x){
		static int st[35];int top=0;
		do{st[top++]=x%10;x/=10;}while(x);
		while(top)putchar(st[--top]+'0');
		return;
	}
	inline void In(){
		n=rnt(),m=rnt(),k=rnt();
		sn=sqrt(n);
		_for(i,1,n)a[i]=rnt();
		_for(i,1,m){
			ll l=rnt(),r=rnt();
			md.Join(l,r,i);
		}
		md.Solve();
		_for(i,1,m)wnt(ans[i]),putchar('\n');
	}
}

[AHOI2013]作业

思路

用莫队离线下来处理,用树状数组维护数的数量和数的种类。

复杂度是 \(\Theta(n\sqrt{m}\log_2{n})\),勉强能过。

代码

点击查看代码
const ll N=1e5+10,P=998244353;
ll n,m,sn,c[N],ans[N][2];
class MODUI{
public:
	ll jl[N];
	class BIT{
	public:
		ll b[N];
		inline void Update(ll x,ll y){while(x&&x<=n)b[x]+=y,x+=(x&-x);}
		inline ll Query(ll x){ll sum=0;while(x)sum+=b[x],x-=(x&-x);return sum;}
	}b1,b2;
	class Q{
	public:
		ll l,r,a,b,id,k;
		inline void Join(ll le,ll ri,ll aa,ll bb,ll i){l=le,r=ri,a=aa,b=bb,id=i,k=(l-1)/sn+1;}
		inline bool operator<(const Q &p){
			if(k!=p.k)return l<p.l;
			if(k&1)return r<p.r;
			return r>p.r;
		}
	}q[N];
	inline void Join(ll le,ll ri,ll aa,ll bb,ll i){q[i].Join(le,ri,aa,bb,i);}
	inline void Add(ll i){
		b1.Update(c[i],1);
		if(!jl[c[i]])b2.Update(c[i],1);
		++jl[c[i]];
	}
	inline void Del(ll i){
		b1.Update(c[i],-1);
		--jl[c[i]];
		if(!jl[c[i]])b2.Update(c[i],-1);
	}
	inline void Solve(){
		sort(q+1,q+m+1);
		ll l=1,r=0;
		_for(i,1,m){
			while(l>q[i].l)Add(--l);
			while(r<q[i].r)Add(++r);
			while(l<q[i].l)Del(l++);
			while(r>q[i].r)Del(r--);
			ans[q[i].id][0]=b1.Query(q[i].b)-b1.Query(q[i].a-1);
			ans[q[i].id][1]=b2.Query(q[i].b)-b2.Query(q[i].a-1);
		}
		return;
	}
}md;
namespace SOLVE{
	inline ll rnt() {
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=x*10+(c^48),c=getchar();
		return x*w;
	}
	inline void wnt(ll x){
		static int st[35];int top=0;
		do{st[top++]=x%10;x/=10;}while(x);
		while(top)putchar(st[--top]+'0');
		return;
	}
	inline void In(){
		n=rnt(),m=rnt();
		sn=sqrt(n);
		_for(i,1,n)c[i]=rnt();
		_for(i,1,m){
			ll l=rnt(),r=rnt(),a=rnt(),b=rnt();
			md.Join(l,r,a,b,i);
		}
		md.Solve();
		_for(i,1,m)wnt(ans[i][0]),putchar(' '),wnt(ans[i][1]),putchar('\n');
		return;
	}
}

Permu

思路

这道题直接用莫队,用分块维护值域上连续块最大值,想起来非常容易。

接下来开始分析上面每道题都在分析的重点内容:块长与时间复杂度。

在分析了半天之后,我分析出了理论最优块长:\(n^{\tfrac{1}{3}}\)

但是交上去之后 TLE 了。

于是,在与这道题痛苦地争斗许久后,我发现直接设块长为 \(700\) 是最优的,同时悟到了块长分析法的真谛:

什么理论最优复杂度!什么估出复杂度解方程!

造组极限数据跑,块长一百一百地加!

跑得最快的就是正确块长,别和我说什么理论,跑不过这个块长就是垃圾!

代码

点击查看代码
const ll N=2e5+10,SN=1000;
ll n,m,sn,sz,a[N],ans[N];
class MODUI{
public:
	class Q{
	public:
		ll i,l,r,k;
		inline void Join(ll id,ll le,ll ri){i=id,l=le,r=ri,k=(l-1)/sz+1;}
		inline bool operator<(const Q &p)const{
			if(k!=p.k)return l<p.l;
			if(k&1)return r<p.r;
			return r>p.r;
		}
	}q[N];
	class BLOCK{
	public:
		ll ka[N],b[N];
		class BL{
		public:
			ll l,r,s;
			ll q,z,h;
			bool full;
		}bl[SN];
		inline void Init(){
			_for(i,1,sn){
				bl[i].l=bl[i-1].r+1;
				bl[i].r=(i==sn)?n:bl[i].l+sz-1;
				bl[i].s=bl[i].r-bl[i].l+1;
				_for(j,bl[i].l,bl[i].r)ka[j]=i;
			}
			return;
		}
		inline void Add(ll i){
			ll k=ka[i],cnt=0;
			b[i]=!b[i],bl[k].full=0;
			bl[k].q=bl[k].z=bl[k].h=0;
			_for(j,bl[k].l,bl[k].r){
				if(!b[j])break;
				++bl[k].q;
			}
			for_(j,bl[k].r,bl[k].l){
				if(!b[j])break;
				++bl[k].h;
			}
			_for(j,bl[k].l,bl[k].r){
				cnt=(cnt+1)*b[j];
				bl[k].z=max(bl[k].z,cnt);
			}
			if(bl[k].q==bl[k].s)bl[k].full=1;
			return;
		}
		inline ll Query(){
			ll num=0,an=0;
			_for(i,1,sn){
				if(bl[i].full)num+=bl[i].s;
				else{
					num+=bl[i].q;
					an=max(an,max(num,bl[i].z));
					num=bl[i].h;
				}
			}
			an=max(an,num);
			return an;
		}
	}bl;
	inline void Join(ll id,ll le,ll ri){q[id].Join(id,le,ri);}
	inline void Solve(){
		sort(q+1,q+m+1);
		ll l=1,r=0;
		bl.Init();
		_for(i,1,m){
			while(l>q[i].l)bl.Add(a[--l]);
			while(r<q[i].r)bl.Add(a[++r]);
			while(l<q[i].l)bl.Add(a[l++]);
			while(r>q[i].r)bl.Add(a[r--]);
			ans[q[i].i]=bl.Query();
		}
		return;
	}
}md;
namespace SOLVE{
	inline ll rnt(){
		ll x=0,w=1;char c=getchar();
		while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
		while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
		return x*w;
	}
	inline void In(){
		n=rnt(),m=rnt();
		sn=700,sz=n/sn;
		_for(i,1,n)a[i]=rnt();
		_for(i,1,m){
			ll x=rnt(),y=rnt();
			md.Join(i,x,y);
		}
		md.Solve();
		_for(i,1,m)printf("%lld\n",ans[i]);
		return;
	}
}

\[\Huge{\mathfrak{The\ End}} \]

posted @ 2022-06-29 17:50  K8He  阅读(209)  评论(5编辑  收藏  举报