例题 (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(nsqrt 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(nsqrt{n}))(这个我不会证明),但是这种方法的编程复杂度稍微高了一点。
有一个比较优雅的替代品:先对序列分块,然后对于所有询问按照(L)所在块的大小排序,如果一样再按照(R)排序,最后再计算。为什么这样计算就可以降低复杂度呢?
一、(i)与(i+1)在同一块内,(r)单调递增,所以(r)是(O(n))的。由于有(sqrt{n})块,所以这一部分时间复杂度是(O(nsqrt{n}));
二、(i)与(i+1)跨越一块,(r)最多变化(n),由于有(sqrt{n})块,所以这一部分时间复杂度是(O(nsqrt{n}));
三、(i)与(i+1)在同一块内时,变化不超过(sqrt{n}),跨越一块也不会超过(sqrt{n}),不妨看作是(sqrt{n})。由于有n个数,所以时间复杂度是(O(nsqrt{n}));
于是就变成了(O(nsqrt{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
",up[i],dw[i]);
}
return 0;
}