HYSBZ2038 小Z的襪子(莫隊算法)

原地址:http://www.cnblogs.com/chanme/p/3681999.html
今天學了一下傳說中的解決離線詢問不修改的一種算法。題目的意思非常簡單,就是詢問在一個[L,R]區間裏的取兩個物品,然後這兩個物品顏色相同的概率。其實就是對於每種顏色i,這個區間裏對應的個數cnt[i],那麼答案就應該是 sigma (cnt[i]cnt[i-1]) / (R-L+1)(R-L). 問題是要是每次詢問我都遍歷一遍的話必T無疑。這個時候莫隊算法就給出了其中一種非常重要的離線處理方法,通過合適的安排詢問的次序降低一定的複雜度。

舉個例子,假如我詢問的時候是詢問[1,2],[1,3],[1,4],[1,5],[1,6]…[1,7],[2,7],顯然我們每次計算這些答案都是O(1)的。但是假如詢問的時候次序變一下

[2,7],[1,3],[1,7],[1,2]….這樣子的話我們就會很蛋疼,所以通過合理的安排區間詢問的順序可以降低複雜度,而莫隊算法就是這樣做的。

首先假如我們已經知道了[L,R]的答案,如果我們可以O(1)的時間得出[L+1,R],[L-1,R],[L,R+1],[L,R-1]的話,那麼下面這種算法就是可以實施的,我們通過對詢問分塊處理,假如區間長度爲n,那麼我們分成sqrt(n)塊,然後對區間排序,排序的時候按照左端點屬於那個塊先排序,然後再按右端點的大小排序。排完序之後,我們就根據當前的[L,R]不斷地去算下一個區間的[Li,Ri].其中就是根據L,Li和R,Ri的差值去變化求出對應的答案。

下面看下複雜度。我們總是從左到右處理一系列的詢問,所以對於同一塊裏的詢問如果有k個的話,那麼由於左端點總是在同一個塊裏動,所以每次左端點動是O(sqrt(n)),而右端點遞增,所以k次的總複雜度不會超過n,因此對於同一塊的操作的總複雜度應該爲 O(k*sqrt(n)+n)

假如詢問的總數爲m的話,那麼由於最多有sqrt(n)塊,所以總複雜度是 m*sqrt(n)+n*sqrt(n).

還有就是當由一個區間轉換到相鄰的另一個區間的時候,左端點移動不超過O(sqrt(n)),右端點移動不超過O(n),最多變動sqrt(n)次。所以轉換區間的時候的複雜度是sqrt(n)*sqrt(n)+n*sqrt(n).

綜上我們可以看出最後的複雜度應該是n^1.5.

這種神奇的分塊處理原來還只是一種簡約版。由上面可以看出,兩次轉移的複雜度取決於[L,R] [Li,Ri]的曼哈頓距離 abs(L-Li)+abs(R-Ri),所以如果能夠合適安排這樣的距離使得總的曼哈頓距離最小,那麼還能再進一步優化,涉及到了曼哈頓最小生成樹的概念,反正我是沒看懂,今天學下這種分塊的思想練練手~

#pragma warning (disable:4996)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;

#define maxn 55000
#define ll long long

ll color[maxn];
ll cnt[maxn];
int n, m;

int pos[maxn];
struct Query
{
    int l, r,id;
    bool operator < (const Query &b) const{
        return pos[l] == pos[b.l] ? r < b.r : pos[l] < pos[b.l];
    }
}q[maxn];

ll gcd(ll a, ll b){
    return a&&b ? gcd(b, a%b) : a + b;
}

ll resl[maxn], resr[maxn];

int main()
{
    while (cin >> n >> m)
    {
        for (int i = 1; i <= n; i++){
            scanf("%lld", color + i);
        }
        for (int i = 1; i <= m; i++){
            scanf("%d%d", &q[i].l, &q[i].r);
            q[i].id = i;
        }
        int bas = int(sqrt(n + .5));
        for (int i = 1; i <= n; i++){
            pos[i] = i / bas;
        }
        memset(cnt, 0, sizeof(cnt));
        sort(q + 1, q + 1 + m);
        int l = 1, r = 1; ll ans = 0;
        cnt[color[1]] ++;
        for (int i = 1; i <= m; i++){
            if (r < q[i].r){
                for (int k = r + 1; k <= q[i].r; k++){
                    ans -= cnt[color[k]] * (cnt[color[k]] - 1);
                    cnt[color[k]]++;
                    ans += cnt[color[k]] * (cnt[color[k]] - 1);
                }
            }
            else if (r>q[i].r){
                for (int k = r ; k >= (q[i].r+1); k--){
                    ans -= cnt[color[k]] * (cnt[color[k]] - 1);
                    cnt[color[k]]--;
                    ans += cnt[color[k]] * (cnt[color[k]] - 1);
                }
            }
            if (l < q[i].l){
                for (int k = l; k <= q[i].l-1; k++){
                    ans -= cnt[color[k]] * (cnt[color[k]] - 1);
                    cnt[color[k]]--;
                    ans += cnt[color[k]] * (cnt[color[k]] - 1);
                }
            }
            else if (l>q[i].l){
                for (int k = l - 1; k >= q[i].l; k--){
                    ans -= cnt[color[k]] * (cnt[color[k]] - 1);
                    cnt[color[k]]++;
                    ans += cnt[color[k]] * (cnt[color[k]] - 1);
                }
            }
            l = q[i].l; r = q[i].r;
            ll len = q[i].r - q[i].l+1;
            ll tot = len*(len - 1);
            ll g = gcd(ans, tot);
            resl[q[i].id] = ans / g; resr[q[i].id] = tot / g;
        }
        for (int i = 1; i <= m; i++){
            printf("%lld/%lld\n", resl[i], resr[i]);
        }
    }
    return 0;
}
發佈了174 篇原創文章 · 獲贊 64 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章