@bzoj - 2388@ 旅行规划
目錄
- @description@
- @solution@
- @accepted code@
- @details@
@description@
請你維護一個序列,支持兩種操作:
(1)某個區(qū)間 [x, y] 內的數(shù)同時加上一個增量 k。
(2)詢問某一個區(qū)間 [x, y] 中從 1 開始的最大前綴和。
input
第一行給出一個整數(shù) n。n <= 100000。接下來一行 n 個整數(shù)表示序列的初始值。
第三行給出一個整數(shù) m,m <= 100000。接下來 m 行每行一個操作。
(1) 0 x y k:表示給區(qū)間 [x, y] 同時加上 k。
(2) 1 x y:詢問區(qū)間 [x, y]。
output
對于每個詢問,輸出一個整數(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@
我們考慮直接維護前綴和序列,則操作(2)就是在查詢區(qū)間最大值。
而對于操作(1),我們相當于兩部分操作:
對于 x <= i <= y,給 i 位置加上 (i-x+1)*k;對于 y < i,給 i 位置加上 (y-x+1)*k。
前一個可以拆成 (-x+1)*k + i*k,是常數(shù) + 系數(shù)*位置的形式;后面那個也可以看成這種形式,只是位置前面的系數(shù)為 0。
看起來還是不好維護,但是我們可以注意到這樣一件事情:對于某一個位置 i,它的值總是形如 k*i + b 的形式。
直線解析式。所以我們考慮用幾何方法來維護這種東西。幾何方法當然首先想到凸包。
每次操作相當于給區(qū)間內所有點的斜率與截距同時加上增量,手算一下會發(fā)現(xiàn)這個區(qū)間相鄰兩點之間的斜率也會同時增加 k,這樣也就是說這個區(qū)間的凸包形狀不會變化。
線段樹不太好搞(況且這道題時限 50s ),我們考慮使用分塊算法來維護凸包。
修改時,散塊暴力修改并重構凸包,整塊打標記,記錄這個區(qū)間整體的斜率與截距變化量。
查詢時,散塊暴力求答案,整塊凸包上二分。
時間復雜度 \(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)這是我第一次寫分塊?原來我以前從來沒寫過這種東西?
把分塊的左端點右端點以及每個點屬于哪個塊先預處理出來感覺比較好寫。
并且把塊的大小設置為常數(shù)也是一個不錯的懶人做法(雖然想想都知道這樣肯定常數(shù)大)。
轉載于:https://www.cnblogs.com/Tiw-Air-OAO/p/10262494.html
總結
以上是生活随笔為你收集整理的@bzoj - 2388@ 旅行规划的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 点分治经典_动态点分治
- 下一篇: 小猿搜题app怎么搜题