一个超级强大的暴力,可以处理一大堆区间问题,效率也不低。美中不足的是这是个离线算法(感觉很多强大的算法都是离线……)。
有些区间问题可以在知道[L,R]的情况下用很短的时间(比如 O(1) 或 O(log2(n)) )求出[L-1,R],[L+1,R],[L,R-1],[L,R+1],这样的话莫队算法就有很大的威力了。
比如求[L,R]中不同颜色的个数,一旦颜色数很大,线段树就不行了。而求不同颜色个数能在 O(1) 内完成递推,所以可以这样做:
1.求出[L,R]。
2.不停求[L-1,R],[L+1,R],[L,R-1],[L,R+1]直到求出下一个问题[L ′ ,R ′ ]。
怎么样,很暴力吧……但是显然如果出现[1,1]和[n,n]交替的数据,n一大就可以卡死上述做法。所以莫队算法将对上述做法进行优化:分块。
将Q个询问按照L分块,分为[1, n√ ],( n√ , 2∗n√ ]……然后按R从小到大排序每个块,然后用上述做法处理即可。但复杂度如何保证?证明如下:
计算次数=L移动次数+R移动次数
L移动次数:
1.相同块中,i到i+1最多移动 n√ 次,总共Q个询问,移动次数为Q* n√ 。
2.不同块中,i到i+1最多移动 n√ 次,总共 n√ 个块,移动次数为n。
R移动次数:
1.相同块中,总计移动最多n次,总共 n√ 个块,移动次数为n* n√ 。
2.不同块中,i到i+1最多移动n次,总共 n√ 个块,移动次数为n* n√ 。
所以莫队算法的理论复杂度为 O((n+Q)∗n√) ,实际处理起来会小很多,特别是随机数据。
以BZOJ1878为例。
就是上面说的问题,不解释了。
#include
#include
#include
using namespace std;
const int maxn=50000,maxm=200000,maxc=1000000;
int n,m,col[maxn+5];
int ans[maxm+5],sum,ha[maxc+5];
struct Ask
{
int L,R,pa,id;
bool operator < (const Ask &a) const {return paq[maxm+5];
bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
int tot=0,f=1;char ch=getchar(),lst='+';
while ('9''0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
if (lst=='-') f=-f;
while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
x=tot*f;
return Eoln(ch);
}
void Update(int pos,int tem) //更新
{
if (!ha[col[pos]]) sum++;
ha[col[pos]]+=tem;
if (!ha[col[pos]]) sum--;
}
int main()
{
freopen("MoTao.in","r",stdin);
freopen("MoTao.out","w",stdout);
readi(n);
for (int i=1;i<=n;i++) readi(col[i]);
readi(m);
for (int i=1,sq=sqrt(n);i<=m;i++)
{
readi(q[i].L);readi(q[i].R);
q[i].pa=(q[i].L-1)/sq+1;q[i].id=i;
}
sort(q+1,q+1+m);
sum=0;for (int i=q[1].L;i<=q[1].R;i++) if (!(ha[col[i]]++)) sum++;
ans[q[1].id]=sum;
for (int i=2;i<=m;i++)
{
int L=q[i-1].L,R=q[i-1].R;
while (L<q[i].L) Update(L++,-1);
while (L>q[i].L) Update(--L,1);
while (R<q[i].R) Update(++R,1);
while (R>q[i].R) Update(R--,-1);
//用上一状态求当前状态
ans[q[i].id]=sum;
}
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
可以解决有单点修改的区间问题。
其实想法很简单,就是加一个时间ti,然后排序时按照L所在块,R所在块,ti大小进行排序。每次除了要移动L和R之外,还要移动ti。不过效率好像很神奇,貌似取决于分块的块大小。网上的大神们块大小都取 n23 ,这样时间复杂度就为 O((n+Q)∗n23) 了,证明如下:
计算次数=L移动次数+R移动次数+ti移动次数
L移动次数:
1.相同L块中,i到i+1最多移动 n23 次,总共Q个询问,移动次数为Q* n23 。
2.不同L块中,i到i+1最多移动 n23 次,总共 n13 个L块,移动次数为n。
R移动次数:
1.相同R块中,i到i+1最多移动 n23 次,总共Q个询问,移动次数为Q* n23 。
2.不同R块中,i到i+1最多移动 n23 次,总共 n23 个R块,移动次数为 n43 。
3.不同L块中,i到i+1最多移动n次,总共 n13 个L块,移动次数为 n43 。
ti移动次数:
1.相同R块中,总计移动最多n次,总共 n23 个R块,移动次数为 n53 。
2.不同R块中,i到i+1最多移动n次,总共 n23 个R块,移动次数为 n53 。
3.不同L块中,i到i+1最多移动n次,总共 n13 个L块,移动次数为 n43 。
综上所述,当块大小为 n23 时,理论复杂度为 O((n+Q)∗n23) 。虽然感觉和证明里一连串式子比起来偏差有点大,不过同普通莫对一样,实际处理起来比理论复杂度小很多,不需要太过担心。
以BZOJ2120为例。
就是上面说的问题,不解释了。
#include
#include
#include
using namespace std;
const int maxn=10000,maxm=10000,maxc=1000000;
int n,m,mQ,mM,col[maxn+5],lst[maxn+5];
int ans[maxm+5],sum,ha[maxc+5];
struct Query
{
int L,R,ti,id,paL,paR;
bool operator < (const Query &a) const
{
if (paLreturn true;
if (paL==a.paL&&paRreturn true;
if (paL==a.paL&&paR==a.paR&&tireturn true;
return false;
}
};
Query Q[maxm+5];
struct Modify
{
int pos,col,lst;
};
Modify M[maxm+5];
bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int readi(int &x)
{
int tot=0,f=1;char ch=getchar(),lst='+';
while ('9''0') {if (ch==EOF) return EOF;lst=ch;ch=getchar();}
if (lst=='-') f=-f;
while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
x=tot*f;
return Eoln(ch);
}
char getrch() {char ch=getchar();while (ch!='Q'&&ch!='R') ch=getchar();return ch;}
void Update(int pos,int tem)
{
if (!ha[col[pos]]) sum++;
ha[col[pos]]+=tem;
if (!ha[col[pos]]) sum--;
}
void Change(int pos,int now,int L,int R) //将pos修改为now
{
if (L<=pos&&pos<=R) Update(pos,-1),col[pos]=now,Update(pos,1); else
col[pos]=now;
}
int main()
{
freopen("MoTao.in","r",stdin);
freopen("MoTao.out","w",stdout);
readi(n);readi(m);
for (int i=1;i<=n;i++) readi(col[i]),lst[i]=col[i];
for (int i=1,si=pow(n,(double)2/3);i<=m;i++)
{
char ch=getrch();int x,y;readi(x);readi(y);
if (ch=='Q')
{
Q[++mQ].L=x;Q[mQ].R=y;Q[mQ].ti=mM;Q[mQ].id=mQ;
Q[mQ].paL=(x-1)/si+1;Q[mQ].paR=(y-1)/si+1;
} else M[++mM].pos=x,M[mM].col=y,M[mM].lst=lst[x],lst[x]=y;
//因为当前x的颜色在改成y之前可能就被改过了,所以要记录lst
}
sort(Q+1,Q+1+mQ);
int L=1,R=1,ti=0;Update(1,1);
for (int i=1;i<=mQ;i++)
{
while (tiwhile (ti>Q[i].ti) Change(M[ti].pos,M[ti].lst,L,R),ti--;
while (L1);
while (L>Q[i].L) Update(--L,1);
while (R1);
while (R>Q[i].R) Update(R--,-1);
ans[Q[i].id]=sum;
}
for (int i=1;i<=mQ;i++) printf("%d\n",ans[i]);
return 0;
}