莫队

莫队

介绍

  • 利用分块进行处理的离线算法;

  • 时间复杂度普遍为 \(O(n\sqrt{n})\)

    但会被卡

实现思路

对答案的查询

  • 在已知区间的情况下暴力寻找的目标区间的贡献;

  • 对于已知当前 \([L,R]\) 区间,一共有 \(4\) 种情况:

    1. 加上当前区间左边一格的贡献:

      Add(--L);

      先将当前的下标移至目标格,然后加上当前格的贡献

    2. 加上当前区间右边一格的贡献:

      Add(++R);

      同理

    3. 减去当前区间最左一格的贡献:

      Sub(L++);

      先减去当前下标的贡献,然后将下标移至目标格

    4. 减去当前区间最右一格的贡献:

      Sub(R--);

      同理

  • 对于需要用到的函数 \(Add\)\(Sub\) 分别为:

    • \(Add(x)\) :按照需求算上第 \(x\) 个元素的贡献;
    • \(Sub(x)\) :按照需求去除第 \(x\) 个元素的贡献;

分块的优化

如果仅仅只按上面的思路进行对答案的查询,当多次询问依次为:

\([1,2],[n-1,n],[1,2],[n-1,n]...\) 时,复杂度便成了 \(O(mn)\) ...

因此,我们需要优化对询问的查询顺序(这就是离线的原因)

  • 将整体的区间分块,块大小为 \(\sqrt{n}\) ,然后再对询问进行排序,进行查找;

  • 对查询操作排序时:

    • 对于左端点 \(L\) 不在同一块的查询,按照 \(L\) 所在块进行排序;
    • 对于左端点 \(L\) 在同一块的查询,按照 \(R\) 的大小排序;
  • 在排序时保留询问的标号,最后按原顺序输出答案;

代码实现

\(Add\)\(Sub\) 函数的实现需要具体按题目要求来。

定义分块

  • 在输入元素时,需要对元素进行分块:

    int n=read();	//输入元素的个数n
    int siz=sqrt(n);//计算块的大小
    for(int i=1;i<=n;i++){
    	a[i]=read();//输入n个元素
    	pos[i]=i/siz;//计算第i个元素所属的块 并储存在pos[i]里
    }
    

    在这里的分块会在后面对询问的排序中用到;

处理查询

  • 定义结构体:

    struct Q{
    	int l,r,k;
    	bool operator<(Q x)const{
            //处理规则如上
    		if(pos[l]==pos[x.l])
    		return r<x.r;
    		return pos[l]<pos[x.l];
    	}
    }q[N];
    /*
    当然,也可以去掉bool operator一句
    用手打排序函数来代替,例如:
    bool cmp(Q x,Q y){
    	if(pos[x.l]==pos[y.l])
    	return x.r<y.r;
    	return pos[x.l]<pos[y.l];
    }
    在主函数中的sort加上“cmp”函数即可
    */
    
  • 进行处理:

    for(int i=1;i<=m;i++){
        //处理区间为[l,r]的查询,询问编号为k
    	q[i].l=read(),q[i].r=read();
    	q[i].k=i;
    }
    sort(q+1,q+m+1);
    

查找答案

  • int l=1,r=0;
    /*
    由于处理的是[l,r]的闭区间
    因此空集应表示为l=r+1;
    不然集合中会存在一个元素
    例如:
    	l=1,r=1时
    	集合为[1,1],存在a[1]这个元素
    */
    for(int i=1;i<=m;i++){
        //处理情况如上
    	while(q[i].l<l)Add(--l);
    	while(q[i].r>r)Add(++r);
    	while(q[i].l>l)Sub(l++);
    	while(q[i].r<r)Sub(r--);
        //由于实际查询的顺序与输入输出不符
        //因此需要改变输出顺序
    	ans[q[i].k]=res;
    }
    

例题

P2709 小B的询问

luogu传送门:题目链接

  • 代码:

    #include<algorithm>
    #include<iostream>
    #include<string.h>
    #include<stdio.h>
    #include<string>
    #include<vector>
    #include<math.h>
    #include<stack>
    #include<queue>
    #include<set>
    #include<map>
    using namespace std;
    int read(){
    	int s=0,f=1;
    	char _z=getchar();
    	while(_z<'0'||_z>'9'){
    		if(_z=='-')
    		f=-1;
    		_z=getchar();
    	}
    	while(_z>='0'&&_z<='9'){
    		s=(s<<3)+(s<<1)+(_z-'0');
    		_z=getchar();
    	}
    	return s*f;
    }
    const int N=5e4+10;
    typedef long long ll;
    int a[N],pos[N],tot[N];
    ll res=0,ans[N];
    struct Q{
    	int l,r,k;
    	bool operator<(Q x){
    		if(pos[l]==pos[x.l])
    		return r<x.r;
    		return pos[l]<pos[x.l];
    	}
    }q[N];
    void Add(int x){
    	x=a[x];
    	res-=tot[x]*tot[x];
    	tot[x]++;
    	res+=tot[x]*tot[x];
    	return;
    }
    void Sub(int x){
    	x=a[x];
    	res-=tot[x]*tot[x];
    	tot[x]--;
    	res+=tot[x]*tot[x];
    	return;
    }
    int main(){
    	int n=read(),m=read();
    	read();
    	int siz=sqrt(n);
    	for(int i=1;i<=n;i++){
    		a[i]=read();
    		pos[i]=i/siz;
    	}
    	for(int i=1;i<=m;i++){
    		q[i].l=read(),q[i].r=read();
    		q[i].k=i;
    	}
    	sort(q+1,q+m+1);
    	int l=1,r=0;
    	for(int i=1;i<=m;i++){
    		while(q[i].l<l)Add(--l);
    		while(q[i].r>r)Add(++r);
    		while(q[i].l>l)Sub(l++);
    		while(q[i].r<r)Sub(r--);
    		ans[q[i].k]=res;
    	}
    	for(int i=1;i<=m;i++){
    		printf("%lld\n",ans[i]);
    	}
    	return 0;
    }
    

P1494 [国家集训队] 小 Z 的袜子

luogu传送门:题目链接

  • 代码:

    #include<algorithm>
    #include<iostream>
    #include<string.h>
    #include<stdio.h>
    #include<string>
    #include<vector>
    #include<math.h>
    #include<stack>
    #include<queue>
    #include<set>
    #include<map>
    using namespace std;
    int read(){
    	int s=0,f=1;
    	char _z=getchar();
    	while(_z<'0'||_z>'9'){
    		if(_z=='-')
    		f=-1;
    		_z=getchar();
    	}
    	while(_z>='0'&&_z<='9'){
    		s=(s<<3)+(s<<1)+(_z-'0');
    		_z=getchar();
    	}
    	return s*f;
    }
    const int N=5e4+10;
    typedef long long ll;
    int a[N],pos[N],tot[N];
    ll res=0,ans[N],fm[N];
    struct Q{
    	int l,r,k;
    	bool operator<(Q x){
    		if(pos[l]==pos[x.l])
    		return r<x.r;
    		return pos[l]<pos[x.l];
    	}
    }q[N];
    void Add(int x){
    	x=a[x];
    	res-=tot[x]*(tot[x]-1)/2;
    	tot[x]++;
    	res+=tot[x]*(tot[x]-1)/2;
    	return;
    }
    void Sub(int x){
    	x=a[x];
    	res-=tot[x]*(tot[x]-1)/2;
    	tot[x]--;
    	res+=tot[x]*(tot[x]-1)/2;
    	return;
    }
    ll gcd(ll x,ll y){
    	int r=x%y;
    	while(r){
    		x=y;
    		y=r;
    		r=x%y;
    	}
    	return y;
    }
    int main(){
    	int n=read(),m=read();
    	int siz=sqrt(n);
    	for(int i=1;i<=n;i++){
    		a[i]=read();
    		pos[i]=i/siz;
    	}
    	for(int i=1;i<=m;i++){
    		q[i].l=read(),q[i].r=read();
    		q[i].k=i;
    	}
    	sort(q+1,q+m+1);
    	int l=1,r=0;
    	for(int i=1;i<=m;i++){
    		while(q[i].l<l)Add(--l);
    		while(q[i].r>r)Add(++r);
    		while(q[i].l>l)Sub(l++);
    		while(q[i].r<r)Sub(r--);
    		ans[q[i].k]=res;
    		fm[q[i].k]=1ll*(r-l+1)*(r-l)/2;
    	}
    	for(int i=1;i<=m;i++){
    		if(ans[i]==0||fm[i]==0){
    			printf("0/1\n");
    			continue;
    		}
    		ll Gcd=gcd(ans[i],fm[i]);
    		printf("%lld/%lld\n",ans[i]/Gcd,fm[i]/Gcd);
    	}
    	return 0;
    }
    /*
    $(\sum{(cnt[i]\times(cnt[i]-1)\div2)})\div((r-l+1)\times(r-l)\div2)$
    */
    
posted @ 2024-04-19 20:23  liuqichen121  阅读(26)  评论(0)    收藏  举报