數據結構入門3—莫隊算法

分治一類的算法大多只能在離線時使用。莫隊算法就是只能在離線下用。

先看一道模板題:小Z的襪子

Description

作爲一個生活散漫的人,小Z每天早上都要耗費很久從一堆五顏六色的襪子中找出一雙來穿。終於有一天,小Z再也無法忍受這惱人的找襪子過程,於是他決定聽天由命……
具體來說,小Z把這N只襪子從1N編號,然後從編號LR(L 儘管小Z並不在意兩隻襪子是不是完整的一雙,甚至不在意兩隻襪子是否一左一右,他卻很在意襪子的顏色,畢竟穿兩隻不同色的襪子會很尷尬。
你的任務便是告訴小Z,他有多大的概率抽到兩隻顏色相同的襪子。當然,小Z希望這個概率儘量高,所以他可能會詢問多個(L,R)以方便自己選擇。

Input

輸入文件第一行包含兩個正整數N和M。N爲襪子的數量,M爲小Z所提的詢問的數量。接下來一行包含N個正整數Ci,其中Ci表示第i只襪子的顏色,相同的顏色用相同的數字表示。再接下來M行,每行兩個正整數L,R表示一個詢問。

Output

包含M行,對於每個詢問在一行中輸出分數A/B表示從該詢問的區間[L,R]中隨機抽出兩隻襪子顏色相同的概率。若該概率爲0則輸出0/1,否則輸出的A/B必須爲最簡分數。(詳見樣例)

我們發現假如用ans1[i]、ans2[i] 表示第i個詢問選到相同襪子的方案數和總方案數,如果知道區間 [l,r]的每個顏色襪子的數量和答案,那麼轉移到 [l,r+1] , [l,r-1] , [l-1,r] , [l+1,r] 是O(1)的,所以就可以把所有詢問區間排一個序,然後直接暴力轉移。

莫隊算法針對這個排序問題。

就是把每個詢問 [l,r] 看做二維平面上的點(l,r),然後轉移的複雜度就是曼哈頓距離,如何讓曼哈頓距離和比較短?分塊。

分塊後按照S形排序,然後就沒有然後了。

//Serene
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=5e4+10;
long long n,m,size,ans1[maxn],ans2[maxn],c[maxn];
long long tot[maxn],ans,l,r;

long long aa;char cc;
long long read() {
	aa=0;cc=getchar();
	while(cc<'0'||cc>'9') cc=getchar();
	while(cc>='0'&&cc<='9') aa=aa*10+cc-'0',cc=getchar();
	return aa;
}

struct Node{
	long long l,r,pos;
}node[maxn];

bool cmp(const Node& a,const Node& b) {
	if(a.l/size!=b.l/size) return a.l/size<b.l/size;
	if((a.l/size)&1) return a.r/size<b.r/size;
	return a.r/size>b.r/size;
}

long long gcd(long long x,long long y) {
	return y==0? x:gcd(y,x%y);
}

int main() {
	n=read();m=read();
	for(int i=1;i<=n;++i) c[i]=read();
	for(int i=1;i<=m;++i) {
		node[i].pos=i;
		node[i].l=read();node[i].r=read();
		ans2[i]=(node[i].r-node[i].l+1)*(node[i].r-node[i].l)/2;
	}
	size=sqrt(n);
	sort(node+1,node+m+1,cmp);
	l=node[1].l;r=node[1].r;
	for(int i=l;i<=r;++i) {
		ans+=tot[c[i]];
		tot[c[i]]++;
	}
	ans1[node[1].pos]=ans;
	for(int i=2;i<=n;++i) {
		while(l>node[i].l) {
			l--;
			ans+=tot[c[l]];
			tot[c[l]]++;
		}
		while(r<node[i].r) {
			r++;
			ans+=tot[c[r]];
			tot[c[r]]++;
		}
		while(l<node[i].l) {
			tot[c[l]]--;
			ans-=tot[c[l]];
			l++;
		}
		while(r>node[i].r) {
			tot[c[r]]--;
			ans-=tot[c[r]];
			r--;
		}
		ans1[node[i].pos]=ans;
	}
	int x;
	for(int i=1;i<=m;++i) {
		if(ans1[i]==0||ans2[i]==0) {
			printf("0/1\n");continue;
		}
		x=gcd(ans1[i],ans2[i]);
		printf("%lld/%lld\n",ans1[i]/x,ans2[i]/x);
	}
	return 0;
}



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