題目鏈接: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;
}