莫隊算法 襪子

排序巧妙優化複雜度,帶來NOIP前的最後一絲寧靜。幾個活蹦亂跳的指針的跳躍次數,決定着莫隊算法的優劣……

·目前的題型概括爲三種:普通莫隊,樹形莫隊以及帶修莫隊。

【例題一】襪子

·述大意:

     進行區間詢問[l,r],輸出該區間內隨機抽兩次抽到相同顏色襪子的概率。

·分析:

     首先考慮對於一個長度爲n區間內的答案如何求解。題目要求Ans使用最簡分數表示:那麼分母就是n*n(表示兩兩襪子之間的隨機組合),分子是一個累加和,累加的內容是該區間內每種顏色i出現次數sum[i]的平方。

     將莫隊算法擡上議程。莫隊算法的思路是,離線情況下對所有的詢問進行一個美妙的SORT(),然後兩個指針l,r(本題是兩個,其他的題可能會更多)不斷以看似暴力的方式在區間內跳來跳去,最終輸出答案。

     掌握一個思想基礎:兩個詢問之間的狀態跳轉。如圖,當前完成的詢問的區間爲[a,b],下一個詢問的區間爲[p,q],現在保存[a,b]區間內的每個顏色出現次數的sum[]數組已經準備好,[a,b]區間詢問的答案Ans1已經準備好,怎樣用這些條件求出[p,q]區間詢問的Ans2?

image

考慮指針向左或向右移動一個單位,我們要付出多大的代價才能維護sum[]和Ans(即使得sum[],Ans保存的是當前[l,r]的正確信息)。我們美妙地對圖中l,r的向右移動一格進行分析:

                                    image

如圖啦。l指針向右移動一個單位,所造成的後果就是:我們損失了一個綠色方塊。那麼怎樣維護?美妙地,sum[綠色]減去1。那Ans如何維護?先看分母,分母從n2變成(n-1)2,分子中的其他顏色對應的部分是不會變的,綠色卻從sum[綠色]2變成(sum[綠色]-1)2 ,爲了方便計算我們可以直接向給Ans減去以前該顏色的答案貢獻(即sum[綠色]2)再加上現在的答案貢獻(即(sum[綠色]-1)2 )。同理,觀賞下面的r指針移動,將是差不多的。

                                      image

·如圖r指針的移動帶來的後果是,我們多了一個橙色方塊。所以操作和上文相似,只不過是sum[橙色]++。

·迴歸正題地,我們美妙的發現,知道一個區間的信息,要求出旁邊區間的信息(旁邊區間指的是當前區間的一個指針通過加一減一得到的區間),竟只需要O(1)的時間。

·就算是這樣,到這裏爲止的話莫隊算法依舊無法煥發其光彩,原因是:如果我們以讀入的順序來枚舉每個詢問,每個詢問到下一個詢問時都用上述方法維護信息,那麼在你腦海中會浮現出l,r跳來跳去的瘋狂景象,瘋狂之處在於最壞情況下時間複雜度爲:O(n2)————如果要這樣玩,那不如寫一個暴力程序。

·“莫隊算法巧妙地將詢問離線排序,使得其複雜度無比美妙……”在一般做題時我們時常遇到使用排序來優化枚舉時間消耗的例子。莫隊的優化基於分塊思想:對於兩個詢問,若在其l在同塊,那麼將其r作爲排序關鍵字,若l不在同塊,就將l作爲關鍵字排序(這就是雙關鍵字)。大米餅使用Be[i]數組表示i所屬的塊是誰。排序如:

image

·值得強調的是,我們是在對詢問進行操作。

·時間複雜度分析(分類討論思想):

首先,枚舉m個答案,就一個m了。設分塊大小爲unit。

分類討論:

①l的移動:若下一個詢問與當前詢問的l所在的塊不同,那麼只需要經過最多2*unit步可以使得l成功到達目標.複雜度爲:O(m*unit)

②r的移動:r只有在Be[l]相同時纔會有序(其餘時候還是瘋狂地亂跳,你知道,一提到亂跳,那麼每一次最壞就要跳n次!),Be[l]什麼時候相同?在同一塊裏面l就Be[]相同。對於每一個塊,排序執行了第二關鍵字:r。所以這裏面的r是單調遞增的,所以枚舉完一個塊,r最多移動n次。總共有n/unit個塊:複雜度爲:O(n*n/unit)

總結:O(n*unit+n*n/unit)(n,m同級,就統一使用n)

根據基本不等式得:當n爲sqrt(n)時,得到莫隊算法的真正複雜度:

O(n*sqrt(n))

·代碼上來了(莫隊喜歡while):

#include<iostream>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
const int N = 50003;
struct node {
	int l, r, id;
	LL a, b;
}q[N];
LL gcd(LL a, LL b) {
	return !b ? a : gcd(b, a%b);
}
int n, m, col[N], unit, be[N];
LL sum[N], ans = 0;
bool cmp1(node a, node b) {
	return be[b.l] == be[a.l] ? a.r < b.r : a.l < b.l;
}
bool cmp2(node a, node b) {
	return a.id < b.id;
}
void revise(int x, int add) {
	ans -= sum[col[x]] * sum[col[x]];
	sum[col[x]] += add;
	ans += sum[col[x]] * sum[col[x]];
}
int main() {
	scanf("%d%d", &n, &m);
	unit = sqrt(n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &col[i]);
		be[i] = i / unit + 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, cmp1);
	int l = 1, r = 0;
	for (int i = 1; i <= m; i++) {
		while (l < q[i].l) {
			revise(l, -1);
			l++;
		}
		while (l > q[i].l) {
			revise(l - 1, 1);
			l--;
		}
		while (r < q[i].r) {
			revise(r + 1, 1);
			r++;
		}
		while (r > q[i].r) {
			revise(r, -1);
			r--;
		}

		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 = 1 * (q[i].r - q[i].l + 1)*(q[i].r - q[i].l);
		LL g = gcd(q[i].a, q[i].b);
		q[i].a /= g;
		q[i].b /= g;
	}
	sort(q + 1, q + m + 1, cmp2);
	for (int i = 1; i <= m; i++)printf("%lld/%lld\n", q[i].a, q[i].b);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章