1. 程式人生 > >ACM~離線莫隊算法

ACM~離線莫隊算法

次數 return lines 優劣 之前 con void tutorial lin

·莫隊算法被大家稱為“優雅的暴力”

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

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

照常在這引用兩篇我學習莫隊的博客

https://www.cnblogs.com/Paul-Guderian/p/6933799.html

https://www.cnblogs.com/RabbitHu/p/MoDuiTutorial.html

莫隊算法其實是一個非常容易學習的一個算法,它靈活的分塊排序讓我們的復雜度神奇的降低下來

我在這裏只講述離線莫隊

來個題 SPOJ - DQUERY

Given a sequence of n numbers a1

, a2, ..., an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, ..., aj.

Input

  • Line 1: n (1 ≤ n ≤ 30000).
  • Line 2: n numbers a1, a2, ..., an (1 ≤ ai ≤ 106).
  • Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
  • In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).

Output

  • For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, ..., aj in a single line.

Example

Input
5
1 1 2 1 3
3
1 5
2 4
3 5

Output
3
2
3 


題意:求區間內的不同數字個數;
解法:
第一步:
本來如果我們使用純暴力,每個都遍歷的話,n範圍10^4,有q次查詢,範圍10^5,然後我們遍歷左區間到右區間又是n10^4
完美的10^13,大爆炸,

第二步:
然後我們想怎麽去優化,想想能不能利用之前的查詢結果,就像尺取法,每次只是更新移動左右端點,我們試試
我們開始保存了1 5的結果,用個數組記錄這個區間的出現的數及其次數,然後我們在移動左右端點的時候,只要判斷這個數的次數減去一個之後是否為零
加的時候,之前是否為0,就能加減這個區間不同數字的個數,但是我們又想一下,如果他的查詢是1 N 然後 (1+n)/2,(1+n)/2....使每次的端點移動
步數最大,那我們之前做的優化相當於沒做,

第三步:
那我們是否可以使我們查詢的波動小一點呢,答案是可以的,我們只要改變查詢的順序,使每次的端點移動步數
盡量的小即可,那我們就有一個固定的排序方法,“分塊”,我們把n分成“根號n”塊,給每個塊加個編號,
然後我們排序方式:在我們m次查詢中,編號小的在前,如果同在一個塊內,右端點小的在前

以上第二步+第三步就是我們神奇的莫隊,核心思想:利用前面查詢的結果,然後我們移動端點的次數盡量少,分塊的個數是根號n
因為左端點是以根號n分塊的,所以兩個區間最多相差一個塊,右端點可以從1到n所以復雜度是O(n*根號n)

然後我們理解莫隊就可以敲這個題了
#include<cstdio>
#include<cstring>
#include<algorithm>
#define B 174 //根號30000
#define bel(x) ((x - 1) / B + 1) //求一個區間在第幾個塊 using namespace std; struct sss { int id; int left,right; friend bool operator <(struct sss x,struct sss y)//莫隊排序方法 { return bel(x.left)==bel(y.left)?x.right<y.right:x.left>y.left; } }q[200001]; int cnt[1000001]; int a[30001]; int b[200001]; int sum=0; void del(int x) { cnt[x]--; if(!cnt[x]) sum--; } void add(int x) { if(!cnt[x]) sum++; cnt[x]++; } int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } int m; scanf("%d",&m); for(int i=1;i<=m;i++) { q[i].id=i; scanf("%d%d",&q[i].left,&q[i].right); } sort(q+1,q+m+1); int l=1,r=0; for(int i=1;i<=m;i++) { while(l<q[i].left) del(a[l++]);//移動端點
while(l>q[i].left) add(a[--l]); while(r<q[i].right) add(a[++r]); while(r>q[i].right) del(a[r--]); b[q[i].id]=sum; } for(int i=1;i<=m;i++) { printf("%d\n",b[i]); } }

 
 

ACM~離線莫隊算法