@bzoj - 2388@ 旅行规划
目錄
- @description@
- @solution@
- @accepted code@
- @details@
@description@
請(qǐng)你維護(hù)一個(gè)序列,支持兩種操作:
(1)某個(gè)區(qū)間 [x, y] 內(nèi)的數(shù)同時(shí)加上一個(gè)增量 k。
(2)詢問某一個(gè)區(qū)間 [x, y] 中從 1 開始的最大前綴和。
input
第一行給出一個(gè)整數(shù) n。n <= 100000。接下來一行 n 個(gè)整數(shù)表示序列的初始值。
第三行給出一個(gè)整數(shù) m,m <= 100000。接下來 m 行每行一個(gè)操作。
(1) 0 x y k:表示給區(qū)間 [x, y] 同時(shí)加上 k。
(2) 1 x y:詢問區(qū)間 [x, y]。
output
對(duì)于每個(gè)詢問,輸出一個(gè)整數(shù)表示最大前綴和。
sample input
5
1 8 -8 3 -7
3
1 1 5
0 1 3 6
1 2 4
sample output
9
22
@solution@
我們考慮直接維護(hù)前綴和序列,則操作(2)就是在查詢區(qū)間最大值。
而對(duì)于操作(1),我們相當(dāng)于兩部分操作:
對(duì)于 x <= i <= y,給 i 位置加上 (i-x+1)*k;對(duì)于 y < i,給 i 位置加上 (y-x+1)*k。
前一個(gè)可以拆成 (-x+1)*k + i*k,是常數(shù) + 系數(shù)*位置的形式;后面那個(gè)也可以看成這種形式,只是位置前面的系數(shù)為 0。
看起來還是不好維護(hù),但是我們可以注意到這樣一件事情:對(duì)于某一個(gè)位置 i,它的值總是形如 k*i + b 的形式。
直線解析式。所以我們考慮用幾何方法來維護(hù)這種東西。幾何方法當(dāng)然首先想到凸包。
每次操作相當(dāng)于給區(qū)間內(nèi)所有點(diǎn)的斜率與截距同時(shí)加上增量,手算一下會(huì)發(fā)現(xiàn)這個(gè)區(qū)間相鄰兩點(diǎn)之間的斜率也會(huì)同時(shí)增加 k,這樣也就是說這個(gè)區(qū)間的凸包形狀不會(huì)變化。
線段樹不太好搞(況且這道題時(shí)限 50s ),我們考慮使用分塊算法來維護(hù)凸包。
修改時(shí),散塊暴力修改并重構(gòu)凸包,整塊打標(biāo)記,記錄這個(gè)區(qū)間整體的斜率與截距變化量。
查詢時(shí),散塊暴力求答案,整塊凸包上二分。
時(shí)間復(fù)雜度 \(O(n\sqrt{n}\log n)\)
@accepted code@
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int MAXN = 100000; const int BLOCK = 320; const ll INF = (1ll<<62); ll sp[BLOCK + 5], b1[MAXN + 5], b2[BLOCK + 5]; int le[BLOCK + 5], ri[BLOCK + 5], num[MAXN + 5]; int stk[MAXN + 5], tp[BLOCK + 5], n, m, bcnt = 0; ll get_ans(int x) {return sp[num[x]]*x + b1[x] + b2[num[x]]; } ll query(int x) {int l = le[x], r = tp[x];while( l < r ) {int mid = (l + r) >> 1;if( get_ans(stk[mid]) >= get_ans(stk[mid+1]) ) r = mid;else l = mid + 1;}return get_ans(stk[r]); } void push_tag(int x) {for(int i=le[x];i<=ri[x];i++)b1[i] = get_ans(i);sp[x] = b2[x] = 0; } void build(int x) {tp[x] = le[x] - 1;for(int i=le[x];i<=ri[x];i++) {while( tp[x] > le[x] && (get_ans(i) - get_ans(stk[tp[x]]))*(stk[tp[x]] - stk[tp[x] - 1]) >= (get_ans(stk[tp[x]])-get_ans(stk[tp[x] - 1]))*(i - stk[tp[x]]) )tp[x]--;stk[++tp[x]] = i;} } void init() {for(int i=0;i<n;i++) {if( i % BLOCK == 0 ) {num[i] = (++bcnt);le[num[i]] = ri[num[i]] = i;sp[num[i]] = b2[num[i]] = 0;}else ri[num[i] = bcnt]++;} } int main() {scanf("%d", &n); init();for(int i=0;i<n;i++)scanf("%lld", &b1[i]), b1[i] += b1[i-1];for(int i=1;i<=bcnt;i++)build(i);scanf("%d", &m);for(int i=1;i<=m;i++) {int op, x, y; ll k;scanf("%d%d%d", &op, &x, &y);x--, y--;if( op == 0 ) {scanf("%lld", &k);if( num[x] != num[y] ) {push_tag(num[x]), push_tag(num[y]);for(int i=x;i<=ri[num[x]];i++)b1[i] += k*(i-x+1);for(int i=le[num[y]];i<=y;i++)b1[i] += k*(i-x+1);for(int i=y+1;i<=ri[num[y]];i++)b1[i] += k*(y-x+1);build(num[x]), build(num[y]);for(int i=num[x]+1;i<=num[y]-1;i++)sp[i] += k, b2[i] += k*(-x+1);}else {push_tag(num[x]);for(int i=x;i<=y;i++)b1[i] += k*(i-x+1);for(int i=y+1;i<=ri[num[y]];i++)b1[i] += k*(y-x+1);build(num[x]);}for(int i=num[y]+1;i<=bcnt;i++)b2[i] += k*(y-x+1);}else {ll ans = -INF;if( num[x] != num[y] ) {for(int i=x;i<=ri[num[x]];i++)ans = max(ans, get_ans(i));for(int i=le[num[y]];i<=y;i++)ans = max(ans, get_ans(i));for(int i=num[x]+1;i<=num[y]-1;i++)ans = max(ans, query(i));}else {for(int i=x;i<=y;i++)ans = max(ans, get_ans(i));}printf("%lld\n", ans);}} }@details@
突然發(fā)現(xiàn)這是我第一次寫分塊?原來我以前從來沒寫過這種東西?
把分塊的左端點(diǎn)右端點(diǎn)以及每個(gè)點(diǎn)屬于哪個(gè)塊先預(yù)處理出來感覺比較好寫。
并且把塊的大小設(shè)置為常數(shù)也是一個(gè)不錯(cuò)的懶人做法(雖然想想都知道這樣肯定常數(shù)大)。
轉(zhuǎn)載于:https://www.cnblogs.com/Tiw-Air-OAO/p/10262494.html
總結(jié)
以上是生活随笔為你收集整理的@bzoj - 2388@ 旅行规划的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 点分治经典_动态点分治
- 下一篇: 【博 客 文 章】