分塊 以poj3468和洛谷P4168為例

2021-10-10 03:05:46 字數 4384 閱讀 2412

當我們遇到簡單的區間問題時,一般會想到樹狀陣列或者線段樹。但是當區間資訊不符合區間可加可減性時,我們就要另尋他路了。這時我們可以選擇用分塊來解決問題。

分塊要將你要處理的區間劃分成長度大致相等的幾塊。為什麼是大致相等呢?因為最後乙個剩下的區間長度是難以控制的,剩下來的長度我們就讓它自己成為乙個區間,大概是這個樣子。

對於分塊來說,我們要先記錄下每個區間的左右極限,以及每個點所在的區間,比如點1

11在第乙個區間,點5

55在第二個區間,之後在進行區間處理的時候,訪問它們所在的區間。對於剛開始的資訊還要進行預處理。

我們以poj3468為例。這是一道很常規的線段樹區間更新,區間求和的板子題。我們將區間以n

\sqrt

n​為長度劃分,用分塊寫出來大概是這樣的。

#include

#include

#include

#include

#include

using

namespace std;

typedef

long

long ll;

const

int maxn =

1e5+7;

int l[maxn]

, r[maxn]

, pos[maxn]

;ll sum[maxn]

, a[maxn]

, c, lazy[maxn]

;int n, m, t, ql, qr;

char op[1]

;void

add(

int ql,

int qr, ll c)

sum[pl]

+= c *

(qr - ql +1)

;return;}

for(

int i = ql; i <= r[pl]

;++i)

sum[pl]

+= c *

(r[pl]

- ql +1)

;for

(int i = l[pr]

; i <= qr;

++i)

sum[pr]

+= c *

(qr - l[pr]+1

);for(

int i = pl +

1; i < pr;

++i)

}ll query

(int ql,

int qr)

return res;

}for

(int i = ql; i <= r[pl]

;++i) res +

= a[i]

+ lazy[pl]

;for

(int i = l[pr]

; i <= qr;

++i) res +

= a[i]

+ lazy[pr]

;for

(int i = pl +

1; i < pr;

++i) res +

= sum[i]

+ lazy[i]

*(r[i]

- l[i]+1

);return res;

}int

main()

if(n > t * t)

for(

int i =

1; i <= t;

++i)

}while

(m--

)else

}return0;

}

到這裡可能還會覺得分塊沒有什麼用,在**量和時間複雜度上都不如別的資料結構。但是如果我們遇到了洛谷p4168這型別的題目,就無法簡單地用線段樹和樹狀陣列去處理。

tt。由於這道題目的數值很大,我們採取離散化處理。離散化之後,我們先確定好各點所在的區間,然後預處理num

[i][

j]num[i][j]

num[i]

[j],代表某個區間到某個區間的編號是i

ii,其中第j

jj個顏色出現了多少次,並用res

resre

s陣列來記錄當前區間的答案,總共有t

2t^2

t2個區間。這樣預處理之後,我們在進行訪問的時候,就可以直接得到中間一段塊的資訊,就只用處理邊邊角角的資料。同時應該注意到,若訪問的是處於同一區間的點,或者是相鄰區間的點,都要進行遍歷。

然後我們發現預處理需要o(t

2n)o(t^2n)

o(t2n)

,分塊的長度是n/t

n/tn/

t,所以我們m

mm次詢問需要o(m

n/t)

o(mn/t)

o(mn/t

)。我們令t2n

=mn/

tt^2n = mn/t

t2n=mn

/t,m

mm和n

nn為同乙個數量級,可得t

tt為n

3\sqrt[3]

3n​。所以確定了t

tt的大小。

#include

using

namespace std;

typedef

long

long ll;

const

int maxn =

4e4+7;

int num[

1000

][maxn]

;int l[maxn]

, r[maxn]

, pos[maxn]

, res[

1000

], ma[

1000];

int cor[maxn]

, a[maxn]

, cnt[

1000

][maxn]

, c[maxn]

;int n, m, t, ql, qr, len, tot;

intquery

(int ql,

int qr)

for(

int i = ql; i <= qr;

++i) c[cor[i]]--

;return ans;

} ans = res[cnt[pl +1]

[pr -1]

];maxx = ma[cnt[pl +1]

[pr -1]

];for(

int i = ql; i <= r[pl]

;++i)

else

if(c[cor[i]

]+ num[cnt[pl +1]

[pr -1]

][cor[i]

]== maxx)

}for

(int i = l[pr]

; i <= qr;

++i)

else

if(c[cor[i]

]+ num[cnt[pl +1]

[pr -1]

][cor[i]

]== maxx)

}for

(int i = ql; i <= r[pl]

;++i) c[cor[i]]--

;for

(int i = l[pr]

; i <= qr;

++i) c[cor[i]]--

;return ans;

}int

main()

if(n > r[t]

)for

(int i =

1; i <= n;

++i)

sort

(a +

1, a +

1+ n)

; tot =

unique

(a +

1, a +

1+ n)

- a -1;

for(

int i =

1; i <= n;

++i)

int now =0;

for(

int i =

1; i <= t;

++i)

else

if(num[cnt[i]

[j]]

[cor[k]

]== maxx && cor[k]

< res[cnt[i]

[j]]

) res[cnt[i]

[j]]

= cor[k];}

}}int pre =0;

while

(m--

)return0;

}

線段樹區間更新(以POJ 3468為例)

在了解單點更新的線段樹的前提下,再繼續理解區間更新的線段樹。區間更新是指更新某個區間內的葉子節點的值,因為涉及到的葉子節點不止乙個,而葉子節點會影響其相應的非葉父節點,那麼回溯需要更新的非葉子節點也會有很多,如果一次性更新完,操作的時間複雜度肯定不是o lgn 為此引入了線段樹中的延遲標記概念,這也...

poj3468之線段樹

include include include include include include include include include define inf 99999999 using namespace std const int max 100000 10 int64 sum max ...

poj 3468 線段樹 懶標記

簡單的線段樹區間求和問題,每次改變區間值得時候採用懶標記的操作,等到下次經過這個區間的時候再將懶標記向下傳遞。include include using namespace std int n,q int a 100005 long long sum 500005 lazy 500005 void ...