【莫隊】HH的項鍊 && 小Z的襪子

昨天早上別人莫隊都65+,只有我20分。【好歹水了20分html

話說當時莫隊仍是我講的來着。QAQ前端

因而我就來頹博客了【實際上是改不出來題了node


 

背景

  莫隊!優美的暴力!ios

  聽說這是2010年國家集訓隊的莫濤在做業裏提到了這個方法。算法

  因爲莫濤常常打比賽作隊長,你們都叫他莫隊,該算法也被稱爲莫隊算法。數組

  


 

前置知識

  1.簡單 分塊ide

  2.熟練離散化工具

  3.倍增 / 樹剖 求LCA優化

  4.STL中有關sort的部分ui


例題1. P1972 [SDOI2009]HH的項鍊

題目描述

HH 有一串由各類漂亮的貝殼組成的項鍊。HH 相信不一樣的貝殼會帶來好運,因此每次散步完後,他都會隨意取出一段貝殼,思考它們所表達的含義。HH 不斷地收集新的貝殼,所以,他的項鍊變得愈來愈長。有一天,他忽然提出了一個問題:某一段貝殼中,包含了多少種不一樣的貝殼?這個問題很難回答……由於項鍊實在是太長了。因而,他只好求助睿智的你,來解決這個問題。

輸入格式

第一行:一個整數N,表示項鍊的長度。

第二行:N 個整數,表示依次表示項鍊中貝殼的編號(編號爲0 到1000000 之間的整數)。

第三行:一個整數M,表示HH 詢問的個數。

接下來M 行:每行兩個整數,L 和R(1 ≤ L ≤ R ≤ N),表示詢問的區間。

輸出格式

M 行,每行一個整數,依次表示詢問對應的答案。

輸入輸出樣例

輸入 #1
6
1 2 3 4 3 5
3
1 2
3 5
2 6
輸出 #1
2
2
4

說明/提示

數據範圍:

對於100%的數據,N <= 500000,M <= 500000。


思路分析

  一眼線段樹。【線段樹可作,主席樹也可作QAQ可是我不會

  那咱們來想一下,若是直接暴力會怎麼樣?

      用一個 cn數組記錄每一個數值出現的次數,再暴力枚舉 l 到 統計次數,最後再掃一遍cnt數組,統計cnt不爲零的數值個數,輸出答案便可。

      設最大數值爲s,那麼這樣作的複雜度爲O(m(n+s))O(n2),對於本題實在跑不起。

      必然TLE。

  怎麼樣才能減小浪費呢?

  咱們能夠記錄一個num數組,增長出現次數時判斷一下cnt[ num ]是否爲0,

    若是爲0,則這個數值以前沒有出現過,如今出現了,數值數固然要+1。

    一樣在從區間中刪除num後也判斷一下cnt[ num ]是否爲0,若是爲0 數值總數-1。

  這樣咱們優化掉了一個O(ms),可是仍是沒什麼用處,依然會TLE。

  那麼咱們再加上兩個指針,對於每個詢問不直接掃,而是使用上一個詢問的數值經過移動指針來修改,獲得當前的答案。

  咱們發現左右指針最壞狀況下均移動了O(nm)次,雖然O(1)更新答案,總時間複雜度仍然是O(nm),在最壞狀況下跑得比慢的一批。

  那麼究竟問題出如今了哪裏?

  我沒畫圖工具因而準備口述。

  仔細想一想,當一個詢問出如今序列的很前段,下一個在最後段,再下一個又在最前端/..........................

  冗餘的地方顯而易見了。

  這個時候就大概能夠想到了,將詢問排序,儘可能減小指針移動的損失。

  基本上接近了莫隊的精髓。

莫隊的本質

  莫隊算法的核心是分塊和排序

  將大小爲n的序列分爲n−√n個塊,從1n−√n編號,而後根據這個對查詢區間進行排序。

  把查詢區間按照左端點所在塊的序號排個序,若是左端點所在塊相同,再按右端點排序。

  排完序後咱們再進行左右指針跳來跳去的操做,雖然看似沒多大用,但帶來的優化實際上極大。

inline bool cmp(Node x,Node y) {
    return pos[x.l]==pos[y.l]?x.r<y.r:x.l<y.l;
}

  據說還有個玄學優化:奇偶性排序

  這個和莫隊的主算法有殊途同歸之妙……看起來卵用都沒有,實際上能夠幫你每一個點平均優化200ms(可怕)

                                              ------WAMonster

  順便再引用一下DALAO的代碼。

int cmp(query a, query b) {
    return (belong[a.l] ^ belong[b.l]) ? belong[a.l] < belong[b.l] : ((belong[a.l] & 1) ? a.r < b.r : a.r > b.r);
}

也就是說,對於左端點在同一奇數塊的區間,右端點按升序排列,反之降序。這個東西也是看着沒用,但實際效果顯著。

它的主要原理即是右指針跳完奇數塊往回跳時在同一個方向能順路把偶數塊跳完,而後跳完這個偶數塊又能順帶把下一個奇數塊跳完。

理論上主算法運行時間減半,實際狀況有所誤差。(不過能優化的很爽就對了

時間複雜度

  排序 O(n logn)+ 左指針 O(n √n)+ 右指針 O(n √n)

  總複雜度 O(n √n)

  強死啦

提供一個分塊思路:

    定義 a[ i ] 表示 i 這個位置的貝殼種類。

    pre[ i ] 表示從 i 往前,第一個種類爲 a[ i ] 的位置。

  咱們要求一段區間 l 到 r 的不一樣種類的貝殼的數量,就是求解區間 [ l , r ]裏有多少位置的 pre[ i ] 小於 l 。

  由於區間內 pre[ i ] 大於 l 的位置都將在區間中出現兩次以上;

   換句話說,一個pre[ i ] 小於 l 的位置,必然是該位置上的貝殼種類在 [ l,r ]中第一次出現的位置,因此咱們只須要統計 pre[ i ] 小於 l 的位置便可。

                                                    ------紫欽

  複雜度OK。


代碼放送(莫隊)

 1 // luogu-judger-enable-o2
 2 #include<iostream>
 3 #include<cstdio>
 4 #include<cstring>
 5 #include<cmath>
 6 #include<algorithm>
 7 using namespace std;
 8 
 9 const int MA=500005;
10 int n,m,s,tot;
11 int a[MA],c[MA*2],ans[MA*2];
12 struct Node{
13     int l,r,id;
14     bool operator < (const Node x) const {
15         return l/s==x.l/s?(((l/s)&1)?r<x.r:r>x.r):l<x.l;
16     }
17 }q[MA];
18 
19 inline int read() {
20     char ch=getchar();int num=0;
21     while(ch<'0'||ch>'9') ch=getchar();
22     while(ch>='0'&&ch<='9'){num=(num<<3)+(num<<1)+ch-'0';ch=getchar();}
23     return num;
24 }
25 
26 void add(int x) {
27     tot+=(++c[a[x]]==1);
28     return;
29 }
30 
31 void dec(int x) {
32     tot-=(--c[a[x]]==0);
33     return;
34 }
35 
36 int main()
37 {
38     n=read();
39     s=sqrt(n);
40     for(int i=1;i<=n;++i)
41         a[i]=read();
42     m=read();
43     for(int i=1;i<=m;++i){
44        q[i].l=read();
45        q[i].r=read();
46        q[i].id=i;
47     }
48     sort(q+1,q+m+1);
49     int L=1,R=0;
50     for(int i=1;i<=m;++i){
51         while(q[i].l>L)
52             dec(L++);
53         while(q[i].l<L)    
54             add(--L);
55         while(q[i].r<R)
56             dec(R--);
57         while(q[i].r>R)
58             add(++R);
59         ans[q[i].id]=tot;
60     }
61     for(int i=1;i<=m;++i)
62        printf("%d\n",ans[i]);
63     return 0;
64 }
HH的項鍊

例題2 .P1494 [國家集訓隊]小Z的襪子

題目描述

做爲一個生活散漫的人,小Z天天早上都要耗費好久從一堆五光十色的襪子中找出一雙來穿。終於有一天,小Z再也沒法忍受這惱人的找襪子過程,因而他決定聽天由命……

具體來講,小Z把這N只襪子從1到N編號,而後從編號L到R(L 儘管小Z並不在乎兩隻襪子是否是完整的一雙,甚至不在乎兩隻襪子是否一左一右,他卻很在乎襪子的顏色,畢竟穿兩隻不一樣色的襪子會很尷尬。

你的任務即是告訴小Z,他有多大的機率抽到兩隻顏色相同的襪子。固然,小Z但願這個機率儘可能高,因此他可能會詢問多個(L,R)以方便本身選擇。

然而數據中有L=R的狀況,請特判這種狀況,輸出0/1。

輸入格式

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

輸出格式

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

輸入輸出樣例

輸入 #1
6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6
輸出 #1
2/5
0/1
1/1
4/15

說明/提示

30%的數據中 N,M ≤ 5000;

60%的數據中 N,M ≤ 25000;

100%的數據中 N,M ≤ 50000,1 ≤ L < R ≤ N,Ci ≤ N。


思路分析

  


代碼放送

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cmath>
 5 #include<algorithm>
 6 #define MA 51000
 7 using namespace std;
 8 
 9 inline long long read(){
10     register long long x=0,t=1;
11     register char ch=getchar();
12     while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
13     if(ch=='-'){t=-1;ch=getchar();}
14     while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
15     return x*t;
16 }
17 
18 long long n,m,c[MA];
19 long long color[MA];
20 long long ac;
21 struct ss{
22     long long l,r,id;
23     long long t;
24 }q[MA];
25 
26 struct node{
27     long long a,b;
28 }ans[MA];
29 
30 inline bool cmp(ss a,ss b) {
31     if(a.t==b.t)
32         return a.r<b.r;
33     else
34         return a.t<b.t;
35 }
36 
37 inline void count(long long x,long long k) {
38     ac-=color[c[x]]*color[c[x]];
39     color[c[x]]+=k;
40     ac+=color[c[x]]*color[c[x]];
41 }
42 
43 int main()
44 {
45     n=read();
46     m=read();
47     long long Len=sqrt(n);
48     for(int i=1;i<=n;i++)
49         c[i]=read();
50     for(int i=1;i<=m;i++) {
51         q[i].l=read();
52         q[i].r=read();
53         q[i].id=i;
54         q[i].t=(q[i].l-1)/Len+1;
55     }
56     sort(q+1,q+m+1,cmp);
57     long long l=1,r=0;
58     for(int i=1;i<=m;i++) {
59         while(l>q[i].l) {
60             count(l-1,1);
61             l--;
62         }
63         while(l<q[i].l) {
64             count(l,-1);
65             l++;
66         }
67         while(r>q[i].r) {
68             count(r,-1);
69             r--;
70         }
71         while(r<q[i].r) {
72             count(r+1,1);
73             r++;
74         }
75         if(q[i].l==q[i].r) {
76             ans[q[i].id]=(node){0,1};
77             continue;
78         }
79         ans[q[i].id]=(node){ac-(r-l+1),1LL*(r-l+1)*(r-l)};
80     }
81     for(int i=1;i<=m;i++) {
82         long long d=__gcd(ans[i].a,ans[i].b);
83         if(d>1)
84             printf("%lld/%lld\n",ans[i].a/d,ans[i].b/d);
85         else
86             printf("%lld/%lld\n",ans[i].a,ans[i].b);
87     }
88     return 0;
89 }
小Z的襪子

習題總結

 

謝謝,你們莫隊愉快!!!!!!!!

相關文章
相關標籤/搜索