……

莫队总结

莫队是一种暴力数据结构。
先给一道例题: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)\) 的树套树做法,然而不会......

鸽鸽鸽......

posted @ 2020-05-04 13:56  童话镇里的星河  阅读(152)  评论(0编辑  收藏  举报