莫队算法
例题 (2010年国家集训队论文答辩)小K的袜子
对于询问\([L,R]\),设其中颜色为\(x,y,z...\)的袜子的个数为\(a,b,c...\)
那么答案即为\(\cfrac{\cfrac{a(a-1)}{2}+\cfrac{b(b-1)}{2}+\cfrac{c(c-1)}{2}+...}{\cfrac{(R-L+1)(R-L)}{2}}\)
化简得:\(\cfrac{a^2+b^2+c^2+...x^2-(a+b+c+d+.....)}{(R-L+1)(R-L)}\)
即:\(\cfrac{a^2+b^2+c^2+...x^2-(R-L+1)}{(R-L+1)(R-L)}\)
所以这道题目的关键是求一个区间内每种颜色数目的平方和。
对于一般区间维护类问题,一般用线段树,但是这题完全不知道线段树怎么做,搜索后得知是莫队算法。
莫队算法可以一个可高效解决绝大多数离线+无修改+区间查询问题的算法。这类问题具体是指:如果知道\([L,R]\)的答案时,可以在\(O(\mathrm{g}(n))\)的时间下得到\([L,R-1],\)\([L,R+1],\)\([L-1,R],\)\([L+1,R]\)的答案的话,就可以\(O(n\sqrt n · \mathrm{g}(n))\)的时间内求出所有查询。
对于莫队算法我感觉就是暴力。由于预先知道所有的询问,因此可以合理的组织计算每个询问的顺序以此来降低复杂度。
假设我们算完\([L,R]\)的答案后现在要算\([L',R']\)的答案。由于可以在\(O(1)\)的时间下得到\([L,R-1],\)\([L,R+1],\)\([L-1,R],\)\([L+1,R]\)的答案,所以计算\([L',R']\)的答案耗时\(|L-L'|+|R-R'|\)。如果把询问\([L,R]\)看做平面上的点\(a(L,R)\),询问\([L',R']\)看做点\(b(L',R')\)的话,那么时间开销就为两点的曼哈顿距离。
因此,如果将每个询问看做平面上的一个点,按一定顺序计算每个值,那开销就为曼哈顿距离的和。要计算到每个点,路径至少会形成一棵树。所以问题就变成了求二维平面的最小曼哈顿距离生成树。
这样只要顺着树边计算一次就OK了,可以证明时间复杂度为\(O(n\sqrt{n})\)(这个我不会证明),但是这种方法的编程复杂度稍微高了一点。
有一个比较优雅的替代品:先对序列分块,然后对于所有询问按照\(L\)所在块的大小排序,如果一样再按照\(R\)排序,最后再计算。为什么这样计算就可以降低复杂度呢?
一、\(i\)与\(i+1\)在同一块内,\(r\)单调递增,所以\(r\)是\(O(n)\)的。由于有\(\sqrt{n}\)块,所以这一部分时间复杂度是\(O(n\sqrt{n})\);
二、\(i\)与\(i+1\)跨越一块,\(r\)最多变化\(n\),由于有\(\sqrt{n}\)块,所以这一部分时间复杂度是\(O(n\sqrt{n})\);
三、\(i\)与\(i+1\)在同一块内时,变化不超过\(\sqrt{n}\),跨越一块也不会超过\(\sqrt{n}\),不妨看作是\(\sqrt{n}\)。由于有n个数,所以时间复杂度是\(O(n\sqrt{n})\);
于是就变成了\(O(n\sqrt{n})\)了。
详细过程见代码:
#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<math.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=50010;
typedef long long ll;
ll num[maxn],up[maxn],dw[maxn],ans,aa,bb,cc;
int col[maxn],pos[maxn];
struct qnode
{
int l,r,id;
} qu[maxn];
bool cmp(qnode a,qnode b)
{
if(pos[a.l]==pos[b.l])
return a.r<b.r;
return pos[a.l]<pos[b.l];
}
ll gcd(ll x,ll y)
{
ll tp;
while(tp=x%y)
{
x=y;
y=tp;
}
return y;
}
void update(int x,int d)
{
ans-=num[col[x]]*num[col[x]];
num[col[x]]+=d;
ans+=num[col[x]]*num[col[x]];
}
int main()
{
int n,m,i,j,bk,pl,pr,id;
freopen("in.txt","r",stdin);
while(~scanf("%d%d",&n,&m))
{
memset(num,0,sizeof num);
bk=ceil(sqrt(1.0*n));
for(i=1;i<=n;i++)
{
scanf("%d",&col[i]);
pos[i]=(i-1)/bk;
}
for(i=0;i<m;i++)
{
scanf("%d%d",&qu[i].l,&qu[i].r);
qu[i].id=i;
}
sort(qu,qu+m,cmp);
pl=1,pr=0;
ans=0;
for(i=0;i<m;i++)
{
id=qu[i].id;
if(qu[i].l==qu[i].r)
{
up[id]=0,dw[id]=1;
continue;
}
if(pr<qu[i].r)
{
for(j=pr+1;j<=qu[i].r;j++)
update(j,1);
}
else
{
for(j=pr;j>qu[i].r;j--)
update(j,-1);
}
pr=qu[i].r;
if(pl<qu[i].l)
{
for(j=pl;j<qu[i].l;j++)
update(j,-1);
}
else
{
for(j=pl-1;j>=qu[i].l;j--)
update(j,1);
}
pl=qu[i].l;
aa=ans-qu[i].r+qu[i].l-1;
bb=(ll)(qu[i].r-qu[i].l+1)*(qu[i].r-qu[i].l);
cc=gcd(aa,bb);
aa/=cc,bb/=cc;
up[id]=aa,dw[id]=bb;
}
for(i=0;i<m;i++)
printf("%I64d/%I64d\n",up[i],dw[i]);
}
return 0;
}