题目链接:BZOJ2038
第一次看莫队算法,写写感受。
看的第一篇博客是用平面哈夫曼距离最小生成树写的,看懂了原理,但不会写代码。后来看到了一个更简单的替代品分块,时间复杂度相近,为 n*sqrt(n) 。原理不难理解,基本思路就是通过改变处理询问顺序,降低复杂度。分块版的是按询问的左端点所在块的编号为第一关键字,右端点为第二关键字,排序之后直接暴力就好了。
复杂度分析(每次修改复杂度为 O(1)):
- i与i+1在同一块内,r单调递增,所以r是 O(n) 的。由于有 sqrt(n) 块,所以这一部分时间复杂度是 n*sqrt(n)
- i与i+1跨越一块,r最多变化n,由于 sqrt(n) 块,所以这一部分时间复杂度是 n*sqrt(n)
- i与i+1在同一块内时变化不超过 sqrt(n),跨越一块也不会超过 sqrt(n),忽略系数2。由于有n个数,所以时间复杂度是 n*sqrt(n),
于是就是 O(n*sqrt(n)) 了
然后就是模板题代码(有参考hzwer大神)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=50500;
typedef long long LL;
LL gcd(LL a,LL b)
{
return (b==0)?a:gcd(b,a%b);
}
int pos[maxn],col[maxn],f[maxn],n,m;
struct Query{
int l,r,id;
LL a,b;
friend bool operator < (const Query &R,const Query &T)
{
return (pos[R.l]<pos[T.l])||(pos[R.l]==pos[T].l&&R.r<T.r);
}
void modify()
{
LL k=gcd(a,b);
a/=k,b/=k;
}
}Q[maxn];
bool cmp_id(const Query &a,const Query &b)
{
return a.id<b.id;
}
void init()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&col[i]);
int limit=(int)sqrt((double)n+0.5);
for (int i=1;i<=n;i++)
pos[i]=(i-1)/limit+1;
for (int i=1;i<=m;i++)
{
scanf("%d%d",&Q[i].l,&Q[i].r);
Q[i].id=i;
}
sort(Q+1,Q+m+1);
}
void modify(int p,LL &ans,int add)
{
ans=ans+2*add*f[col[p]]+1;
f[col[p]]+=add;
}
void solve()
{
LL ans=0;
int l=1,r=0;
for (int i=1;i<=m;i++)
{
if (r<Q[i].r)
{
for (r=r+1;r<Q[i].r;r++)
modify(r,ans,1);
modify(r,ans,1);
}
if (Q[i].l<l)
{
for (l=l-1;l>Q[i].l;l--)
modify(l,ans,1);
modify(l,ans,1);
}
if (Q[i].r<r)
{
for (;Q[i].r<r;r--)
modify(r,ans,-1);
}
if (Q[i].l>l)
{
for (;Q[i].l>l;l++)
modify(l,ans,-1);
}
if (Q[i].l==Q[i].r)
{
Q[i].a=0,Q[i].b=1;
continue;
}
Q[i].a=ans-(Q[i].r-Q[i].l+1);
Q[i].b=(LL)(Q[i].r-Q[i].l+1)*(Q[r].r-Q[i].l);
Q[i].modify();
}
sort(Q+1,Q+m+1,cmp_id);
for (int i=1;i<=m;i++)
printf("%lld/%lld\n",Q[i].a,Q[i].b);
}
int main()
{
init();
solve();
return 0;
}