介绍
莫队算法可以一个可高效解决绝大多数离线+无修改+区间查询问题的算法。这类问题具体是指:如果知道的答案时,可以在的时间下得到,,,的答案的话,就可以的时间内求出所有查询。
对于莫队算法我感觉就是暴力。由于预先知道所有的询问,因此可以合理的组织计算每个询问的顺序以此来降低复杂度。
假设我们算完的答案后现在要算的答案。由于可以在的时间下得到,,,的答案,所以计算的答案耗时。如果把询问看做平面上的点,询问看做点的话,那么时间开销就为两点的曼哈顿距离。
因此,如果将每个询问看做平面上的一个点,按一定顺序计算每个值,那开销就为曼哈顿距离的和。要计算到每个点,路径至少会形成一棵树。所以问题就变成了求二维平面的最小曼哈顿距离生成树。
这样只要顺着树边计算一次就OK了,可以证明时间复杂度为(这个我不会证明),但是这种方法的编程复杂度稍微高了一点。
有一个比较优雅的替代品:先对序列分块,然后对于所有询问按照L所在块的大小排序,如果一样再按照R排序,最后再计算。为什么这样计算就可以降低复杂度呢?
一、i与i+1在同一块内,r单调递增,所以r是的。由于有块,所以这一部分时间复杂度是;
二、i与i+1跨越一块,r最多变化n,由于有块,所以这一部分时间复杂度是;
三、i与i+1在同一块内时,变化不超过,跨越一块也不会超过,不妨看作是。由于有n个数,所以时间复杂度是;
于是就变成了了。
例题(bzoj2038)
题意:
有n只袜子,每只袜子有不同的颜色,给出区间,问在这个区间中有多大的概率抽到两只颜色相同的袜子。
思路:
假设区间中有三种颜色,每种颜色的袜子只数分别为a,b,c。
则抽到两只颜色相同的袜子的概率为,化简结果为。
所以只要求每个区间内每种袜子的数目的平方和就可以了。
这时可以用莫队算法了。
代码:
typedef long long ll;
struct qnode
{
int l,r,id;
}q[50010];
ll tot;
struct anode
{
ll fz,fm;
}ans[50010];
int col[50010];
int pos[50010];//在哪一块里(用来对询问排序)
ll num[50010];//当前每种颜色的数目
bool cmp(qnode x,qnode y)
{
if(pos[x.l]!=pos[y.l])return pos[x.l]<pos[y.l];
return x.r<y.r;
}
void update(int id,int d)
{
tot-=num[col[id]]*num[col[id]];
num[col[id]]+=d;
tot+=num[col[id]]*num[col[id]];
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int block=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&col[i]);
pos[i]=(i-1)/block;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
memset(num,0,sizeof(num));
int L=1,R=0;
tot=0;
for(int i=1;i<=m;i++)
{
int id=q[i].id;
if(q[i].l==q[i].r)
{
ans[id].fz=0;
ans[id].fm=1;
continue;
}
if(q[i].l<L)
{
for(int j=L-1;j>=q[i].l;j--)
update(j,1); //区间增加
}
else
{
for(int j=L;j<=q[i].l-1;j++)
update(j,-1); //区间减少
}
L=q[i].l;
if(q[i].r>R)
{
for(int j=R+1;j<=q[i].r;j++)
update(j,1);
}
else
{
for(int j=R;j>=q[i].r+1;j--)
update(j,-1);
}
R=q[i].r;
ans[id].fz=tot-(q[i].r-q[i].l+1);
ans[id].fm=(ll)(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
ll temp=__gcd(ans[id].fz,ans[id].fm);
ans[id].fz/=temp;ans[id].fm/=temp;
}
for(int i=1;i<=m;i++)
printf("%lld/%lld\n",ans[i].fz,ans[i].fm);
return 0;
}
总结
一般来说只要update函数根据题意作出修改就行了,其他都差不多。
小优化
如果l所在块的编号为奇数则按r升序排序,偶数则按r降序排序。
bool cmp(qnode x,qnode y)
{
if(pos[x.l]!=pos[y.l])
return pos[x.l]<pos[y.l];
if(pos[x.l]&1==1)return x.r<y.r;
return x.r>y.r;
}
update
惊了 while循环比for跑得快!考虑到莫队算法是一个很暴力的算法,还是尽可能要优化代码,所以改进一哈。
typedef long long ll;
struct qnode
{
int l,r,id;
}q[50010];
ll tot;
struct anode
{
ll fz,fm;
}ans[50010];
int col[50010];
int pos[50010];//在哪一块里(用来对询问排序)
ll num[50010];//当前每种颜色的数目
bool cmp(qnode x,qnode y)
{
if(pos[x.l]!=pos[y.l])return pos[x.l]<pos[y.l];
return x.r<y.r;
}
void update(int id,int d)
{
tot-=num[col[id]]*num[col[id]];
num[col[id]]+=d;
tot+=num[col[id]]*num[col[id]];
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int block=sqrt(n);
for(int i=1;i<=n;i++)
{
scanf("%d",&col[i]);
pos[i]=(i-1)/block;
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
memset(num,0,sizeof(num));
int L=1,R=0;
tot=0;
for(int i=1;i<=m;i++)
{
int id=q[i].id;
if(q[i].l==q[i].r)
{
ans[id].fz=0;
ans[id].fm=1;
continue;
}
while(L>q[i].l)
{
L--;
update(L,1);
}
while(L<q[i].l)
{
update(L,-1);
L++;
}
while(R<q[i].r)
{
R++;
update(R,1);
}
while(R>q[i].r)
{
update(R,-1);
R--;
}
ans[id].fz=tot-(q[i].r-q[i].l+1);
ans[id].fm=(ll)(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
ll temp=__gcd(ans[id].fz,ans[id].fm);
ans[id].fz/=temp;ans[id].fm/=temp;
}
for(int i=1;i<=m;i++)
printf("%lld/%lld\n",ans[i].fz,ans[i].fm);
return 0;
}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!