1. 程式人生 > >【BZOJ2038】小Z的襪子,第一次的莫隊演算法

【BZOJ2038】小Z的襪子,第一次的莫隊演算法

傳送門
寫在前面:莫隊竟如此暴力……
思路:當初我對這個題的第一感覺——這個區間問題可以用線段樹或者樹狀陣列?答案當然是不能,於是我就去簡單學了下莫隊演算法。在我看來,莫隊(分塊版,不是二維曼哈頓距離什麼什麼最小生成樹)就是分塊排序優化暴力查詢,減少查詢區間之間的覆蓋長度,從而優化時間複雜度,有一種說法很精彩

如果我們已知[l,r]的答案,能在O(1)時間得到[l+1,r]的答案以及[l,r-1]的答案,即可使用莫隊演算法。時間複雜度為O(n^1.5)。如果只能在logn的時間移動區間,則時間複雜度是O(n^1.5*log
n)。 其實就是找一個數據結構支援插入、刪除時維護當前答案。

這道題的話我們很容易用陣列來實現,做到O(1)的從[l,r]轉移到[l,r+1]與[l+1,r]。

那麼莫隊演算法怎麼做呢?以下都是在轉移為O(1)的基礎下討論的時間複雜度。另外由於n與m同階,就統一寫n。
如果已知[l,r]的答案,要求[l’,r’]的答案,我們很容易通過|l – l’|+|r – r’|次轉移內求得。

對於它的時間複雜度分析

將n個數分成sqrt(n)塊。 按區間排序,以左端點所在塊內為第一關鍵字,右端點為第二關鍵字,進行排序,也就是以(pos [l],r)排序
然後按這個排序直接暴力,複雜度分析是這樣的:
1、i與i+1在同一塊內,r單調遞增,所以r是O(n)的。由於有n^0.5塊,所以這一部分時間複雜度是n^1.5。
2、i與i+1跨越一塊,r最多變化n,由於有n^0.5塊,所以這一部分時間複雜度是n^1.5
3、i與i+1在同一塊內時變化不超過n^0.5,跨越一塊也不會超過n^0.5,忽略*2。由於有n個數,所以時間複雜度是n^1.5
於是就是O(n^1.5)了

這道題是比較模板的莫隊分塊了,對於一個區間詢問[L,R],我們要求的ans是
ans=(Σsum(color[i])1)sum(color[i])/2)/((RL+1)(RL))
簡化可得
ans=(Σ(sum(color[i])2)(RL+1))/((RL+1)(RL))
其中sum(color[i])指第i種顏色在[L,R]中出現的次數
那麼我們現在求出各個詢問區間中sum(color[i])2就行了,具體實現方法參照程式碼
注意:
1.當一種顏色數量ci增加1時,我們可以看出ans=ansci2+(ci+1)2,簡化可得ans=ans+(ci2+1)

,同樣減少1時,ans=ansci2+(ci1)2,簡化得ans=ans+(ci21),這樣做的好處是減少乘法且可用位運算,優化常數(然而並沒有什麼卵用)
2.極限資料50000*50000如果不用longlong會教你做人←_←
程式碼:

#include"bits/stdc++.h"
#define LL long long
using namespace std;
int n,m,last_l=1,last_r;
LL ans,p;
int color[50010],block[50010];
LL sum[50010];
struct os
{
    LL part,l,r,nume,deno;//nume指分子,deno指分母
}q[50010];
int cmp1(os xx,os yy)
{
    if (block[xx.l]<block[yy.l]) return 1;
    if (block[xx.l]>block[yy.l]) return 0;
    return xx.r<yy.r;
}
int cmp2(os xx,os yy){return xx.part<yy.part;}
inline LL gcd(LL x,LL y)
{
    if (!y) return x;
    return gcd(y,x%y);
}
main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&color[i]);
    block[0]=sqrt(n);
    for (int i=1;i<=n;i++)
    block[i]=(i-1)/block[0]+1;
    for (int i=1;i<=m;i++)
    scanf("%d%d",&q[i].l,&q[i].r),
    q[i].part=i;
    sort(q+1,q+m+1,cmp1);   

    for (int i=1;i<=m;i++)
    {
        q[i].deno=(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
        if (last_r<q[i].r)
        {
            for (int j=last_r+1;j<=q[i].r;j++)
            ans+=((sum[color[j]]<<1)+1),
            sum[color[j]]++;
        }
        if (last_r>q[i].r)
        {
            for (int j=last_r;j>q[i].r;j--)
            ans-=((sum[color[j]]<<1)-1),
            sum[color[j]]--;
        }
        if (last_l>q[i].l)
        {
            for (int j=last_l-1;j>=q[i].l;j--)
            ans+=((sum[color[j]]<<1)+1),
            sum[color[j]]++;
        }
        if (last_l<q[i].l)
        {
            for (int j=last_l;j<q[i].l;j++)
            ans-=((sum[color[j]]<<1)-1),
            sum[color[j]]--;
        }
        q[i].nume=ans-(q[i].r-q[i].l+1);
        last_l=q[i].l;
        last_r=q[i].r;
    }
    sort(q+1,q+m+1,cmp2);
    for (int i=1;i<=m;i++)
    {
        if (!q[i].nume){printf("0/1\n");continue;}
        p=gcd(q[i].nume,q[i].deno);
        printf("%lld/%lld\n",q[i].nume/p,q[i].deno/p);
    }
}