题目大意
给一个环,然后求每次修改后的最大子段和,最大子段和不能包含整个子段。
解题思路
以往解决环形问题都是通过将两个相同的数组拼接起来实现的,但是这个题如果这么写的话要考虑区间长度问题,很麻烦。
有一个巧妙的方法,在维护区间最大子段和的同时,再维护一个区间最小子段和,那么考虑两个数组拼接的时候,如果最优解只在左半边或者右半边,那么结果就是维护的最大子段和,如果有交叉,就是区间和-区间最小子段和。可以发现区间最小子段和有三种形式,相应的,去掉之后的区间也有三种形式,可以发现如果剪掉的区间在原来数组的中部,则会剩下前后两个区间,但是因为是环,所以剩下的这两个区间也是连在一起的。
代码
const int maxn = 1e6+10;
struct Tree {
int l, r, lmax, rmax, lmin, rmin, sum, smax, smin;
} tree[maxn<<2];
int n, arr[maxn<<1];
inline void push_down(int rt) {
tree[rt].sum = tree[rt<<1].sum+tree[rt<<1|1].sum;
tree[rt].lmax = max(tree[rt<<1].lmax, tree[rt<<1].sum+tree[rt<<1|1].lmax);
tree[rt].rmax = max(tree[rt<<1|1].rmax, tree[rt<<1|1].sum+tree[rt<<1].rmax);
tree[rt].smax = max(max(tree[rt<<1].smax, tree[rt<<1|1].smax), tree[rt<<1].rmax+tree[rt<<1|1].lmax);
tree[rt].lmin = min(tree[rt<<1].lmin, tree[rt<<1].sum+tree[rt<<1|1].lmin);
tree[rt].rmin = min(tree[rt<<1|1].rmin, tree[rt<<1|1].sum+tree[rt<<1].rmin);
tree[rt].smin = min(min(tree[rt<<1].smin, tree[rt<<1|1].smin), tree[rt<<1].rmin+tree[rt<<1|1].lmin);
}
void build(int rt, int l, int r) {
tree[rt].l = l, tree[rt].r = r;
if (l==r) {
tree[rt].smin = tree[rt].lmin = tree[rt].rmin = tree[rt].smax = tree[rt].lmax = tree[rt].rmax = tree[rt].sum = arr[l];
return;
}
int mid = (l+r)>>1;
build(rt<<1, l, mid);
build(rt<<1|1, mid+1, r);
push_down(rt);
}
void update(int rt, int pos, int val) {
if (tree[rt].l==tree[rt].r) {
tree[rt].smin = tree[rt].lmin = tree[rt].rmin = tree[rt].smax = tree[rt].lmax = tree[rt].rmax = tree[rt].sum = val;
return;
}
int mid = (tree[rt].l+tree[rt].r)>>1;
if (pos<=mid) update(rt<<1, pos, val);
else update(rt<<1|1, pos, val);
push_down(rt);
}
int main() {
while(cin >> n) {
for (int i = 1; i<=n; ++i) scanf("%d", &arr[i]), arr[i+n] = arr[i];
build(1, 1, n);
int m; cin >> m;
while(m--) {
int a, b; scanf("%d%d", &a, &b);
update(1, a, b);
if (tree[1].smax==tree[1].sum) printf("%d
", tree[1].sum-tree[1].smin);
else printf("%d
", max(tree[1].smax, tree[1].sum-tree[1].smin));
}
}
return 0;
}