ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

莫队总结

2020-05-04 14:01:11  阅读:228  来源: 互联网

标签:总结 cnt ch ++ while && include 莫队


莫队是一种暴力数据结构。
先给一道例题:SP3267 DQUERY - D-query
我们很容易想到以一段区间为基础向内伸缩或向外扩展并不断更新答案。
然而我们如果按题目给出的询问顺序更新的话,很容易被卡成 \(\mathcal O(n^2)\) ,这就需要我们给这些询问一个顺序,使得移动次数最小。

关键字的选择:
很容易想到按每个询问的左右端点排序,比如说把左端点看成第一关键字,右端点看成第二关键字。
虽然能过掉此例题,然而跑得巨慢。

其实,莫队是是一种分块思想。
莫队的询问顺序是有套路的,前 \(k-1\) 个关键字按块排序,最后一个按具体位置排序,对于普通莫队,块长为 \(\sqrt{n}\)。
然后进行移动,进行一些无脑的变换即可。

\(Code\):

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int MAXN=30005;
int read()
{
	int x=0,w=1;
	char c=getchar();
	while(c>'9'||c<'0')
	{
		if(c=='-') w=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=(x<<1)+(x<<3)+(c^'0');
		c=getchar();	
	}
	return x*w;
}
struct query
{
	int l,r,id,pos;
}a[200005];
bool cmp(query n,query m)
{
	if(n.pos^m.pos)	return n.pos<m.pos;
	else return n.r<m.r;
} 
int n,m,co[MAXN];
int s=0,l=1,r=0,ans[200005],cnt[1000005],sum,size;
int main()
{
	n=read();
	for(int i=1;i<=n;i++) co[i]=read();
	size=floor(sqrt(n));
	m=read();
	for(int i=1;i<=m;i++)
	{
		a[i].l=read();
		a[i].r=read();
		a[i].id=i;
		a[i].pos=a[i].l/size;
	}
	sort(a+1,a+m+1,cmp);
	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=m;i++)
	{
		while(l>a[i].l) {l--;if(!cnt[co[l]]) s++;cnt[co[l]]++;}
		while(r<a[i].r) {r++;if(!cnt[co[r]]) s++;cnt[co[r]]++;}
		while(l<a[i].l) {cnt[co[l]]--;if(!cnt[co[l]])s--;l++;}
		while(r>a[i].r) {cnt[co[r]]--;if(!cnt[co[r]])s--;r--;}
		ans[a[i].id]=s;
	}
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
} 

容易看出,莫队是一种离线算法。
如果强制在线,那就拜拜吧(
有好多例题:
\(1.\)P1494 [国家集训队]小Z的袜子
\(2.\)P2709 小B的询问

加强:

题目链接:P1903 [国家集训队]数颜色 / 维护队列
带修改没法离线。
你错了......
考虑加一维——时间。
考虑以左端点所在块为第一关键字,右端点所在块为第二关键字,时间(询问编号)为第三关键字。
如果跨越修改时间就修改一下并改变贡献,如果减少至有修改的时间就该回来,改变贡献。
显然,我们要记录一个上一个位置的状态。
容易想到记 \(a_{i,j}\) 为第 \(i\) 个位置 \(j\) 时间的颜色。
然而空间不够,我们考虑类似前向星的东西压缩状态。
记 \(lst_i\) 为第 \(i\) 时间的修改前面的状态是啥,显然这个东西可以复制一下原数组先模拟一遍再说。
然后如果增加修改就是这样的(以例题为例):

while(q[i].t>t)
{
    if(ch[++t])
    {
        if(ch[t]>=ll&&ch[t]<=rr)
        {
            cnt[lst[t]]--;
            if(!cnt[lst[t]]) now--;
            if(!cnt[to[t]]) now++;
            cnt[to[t]]++;
        }
        a[ch[t]]=to[t];
    }
}

其中 cnt[i] 代表第 \(i\) 种颜色出现的次数,ch[i] 为 \(i\) 时刻修改的位置,to[i] 表示 \(i\) 时刻修改成什么颜色,lst[i] 为 \(i\) 时刻修改前 ch[i] 位置的颜色。

减少修改:

while(q[i].t<t)
{
   if(ch[t])
    {
        if(ch[t]>=ll&&ch[t]<=rr)
        {
            cnt[to[t]]--;
            if(!cnt[to[t]]) now--;
            if(!cnt[lst[t]]) now++;
            cnt[lst[t]]++;
        }
        a[ch[t]]=lst[t];
    }
    t--;
}

关于块长,一说 \((n+m)^{\frac{2}{3}}\),一说 \(n^{\frac{3}{4}}\),好像后者快一些,都得卡常
最后的代码(省去火车头):

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;

#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF : *p1++)

char buf[10000000], *p1 = buf, *p2 = buf;
int n,m,x,y;
int a[150000];
int cnt[1000005]={0};
char c;
struct node
{
    int l,r,t,id;
}q[150000];
int cc=0;
int ch[150000],to[150000],h;
int lst[150000],b[150000],ans[150000];

inline bool cmp(node n,node m)
{
    if(n.l/h^m.l/h) return n.l/h<m.l/h;
    else if(n.r/h^m.r/h) return n.r/h<m.r/h;
    else return n.t<m.t;
}

inline int read()
{
    int x=0,w=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        x=(x<<3)+(x<<1)+c-48;
        c=getchar();
    }
    return x*w;
}

inline void print(int x)
{
    if(x>=10) print(x/10);
    putchar(x%10^'0');
}

int main()
{
    n=read(),m=read();
    for(register int i=1;i<=n;i++){a[i]=read();b[i]=a[i];}
    h=pow(n+m,0.666666667);
    //h=sqrt(n);
    for(register int i=1;i<=m;i++)
    {
        c=getchar();
        x=read(),y=read();
        if(c=='Q')
        {
            q[++cc].l=x;
			q[cc].r=y;
            q[cc].t=i;
            q[cc].id=cc;
        }
        else
        {
            if(i==1) a[ch[i]]=to[i];
            ch[i]=x;
			to[i]=y;
            lst[i]=b[x];
            b[x]=y;
        }
    }
    sort(q+1,q+cc+1,cmp);
    register int ll=1,rr=0,t=0,now=0;
    for(register int i=1;i<=cc;i++)
    {
        while(q[i].l<ll){if(!cnt[a[--ll]]) now++;cnt[a[ll]]++;}
        while(q[i].l>ll){cnt[a[ll]]--;if(!cnt[a[ll++]]) now--;}
        while(q[i].r>rr){if(!cnt[a[++rr]]) now++;cnt[a[rr]]++;}
        while(q[i].r<rr){cnt[a[rr]]--;if(!cnt[a[rr--]]) now--;}
        while(q[i].t>t)
        {
            if(ch[++t])
            {
                if(ch[t]>=ll&&ch[t]<=rr)
                {
                    cnt[lst[t]]--;
                    if(!cnt[lst[t]]) now--;
                    if(!cnt[to[t]]) now++;
                    cnt[to[t]]++;
                }
                a[ch[t]]=to[t];
            }
        }
        while(q[i].t<t)
        {
            if(ch[t])
            {
                if(ch[t]>=ll&&ch[t]<=rr)
                {
                    cnt[to[t]]--;
                    if(!cnt[to[t]]) now--;
                    if(!cnt[lst[t]]) now++;
                    cnt[lst[t]]++;
                }
                a[ch[t]]=lst[t];
            }
            t--;
        }
        ans[q[i].id]=now;
    }
    for(register int i=1;i<=cc;i++) print(ans[i]),putchar('\n');
    return 0;
}

有 \(O(n\log ^2n)\) 的树套树做法,然而不会......

鸽鸽鸽......

标签:总结,cnt,ch,++,while,&&,include,莫队
来源: https://www.cnblogs.com/tlx-blog/p/12826369.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有