@bzoj - [email protected] 旅行規劃
目錄
@[email protected]
請你維護一個序列,支援兩種操作:
(1)某個區間 [x, y] 內的數同時加上一個增量 k。
(2)詢問某一個區間 [x, y] 中從 1 開始的最大字首和。
input
第一行給出一個整數 n。n <= 100000。接下來一行 n 個整數表示序列的初始值。
第三行給出一個整數 m,m <= 100000。接下來 m 行每行一個操作。
(1) 0 x y k:表示給區間 [x, y] 同時加上 k。
(2) 1 x y:詢問區間 [x, y]。
output
對於每個詢問,輸出一個整數表示最大字首和。
sample input
5
1 8 -8 3 -7
3
1 1 5
0 1 3 6
1 2 4
sample output
9
22
@[email protected]
我們考慮直接維護字首和序列,則操作(2)就是在查詢區間最大值。
而對於操作(1),我們相當於兩部分操作:
對於 x <= i <= y,給 i 位置加上 (i-x+1)*k;對於 y < i,給 i 位置加上 (y-x+1)*k。
前一個可以拆成 (-x+1)*k + i*k,是常數 + 係數*位置的形式;後面那個也可以看成這種形式,只是位置前面的係數為 0。
看起來還是不好維護,但是我們可以注意到這樣一件事情:對於某一個位置 i,它的值總是形如 k*i + b 的形式。
直線解析式。所以我們考慮用幾何方法來維護這種東西。幾何方法當然首先想到凸包。
每次操作相當於給區間內所有點的斜率與截距同時加上增量,手算一下會發現這個區間相鄰兩點之間的斜率也會同時增加 k,這樣也就是說這個區間的凸包形狀不會變化。
線段樹不太好搞(況且這道題時限 50s ),我們考慮使用分塊演算法來維護凸包。
修改時,散塊暴力修改並重構凸包,整塊打標記,記錄這個區間整體的斜率與截距變化量。
查詢時,散塊暴力求答案,整塊凸包上二分。
時間複雜度 \(O(n\sqrt{n}\log n)\)
@accepted [email protected]
#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);
}
}
}
@[email protected]
突然發現這是我第一次寫分塊?原來我以前從來沒寫過這種東西?
把分塊的左端點右端點以及每個點屬於哪個塊先預處理出來感覺比較好寫。
並且把塊的大小設定為常數也是一個不錯的懶人做法(雖然想想都知道這樣肯定常數大)。