@spoj - gss2@ Can you answer these queries II
@description@
定义一个区间的权值为这个区间所有元素的和(重复的元素只算一次)。
给定一个序列。多次询问。询问某个区间内权值最大的子区间的权值。
input
第一行一个整数 N。1 <= N <= 100000。
第二行是 N 个整数,表示这个序列。保证所有元素的值都在 [-100000, 100000] 的范围内。
第三行一个整数 Q。1 <= Q <= 100000。
接下来 Q 行,每一行两个整数 l, r,表示询问的区间。
output
第 i 行表示对第 i 个询问的答案。
sample input
9
4 -2 -2 3 -1 -4 2 2 -6
3
1 2
1 5
4 9
sample output
4
5
3
@solution@
可能是 GSS 系列里面最有趣的一道题了?
这道题提出来一种解决维护询问区间的子区间信息的方法:
首先离线化处理,将所有询问按照右端点排序。
然后,从左往右扫描序列,每一个位置存储它作为左端点的最优值。
扫描到一个新位置时,以这个位置作为右端点去更新前面的值。
查询在询问右端点的位置处理,查询区间内所有位置作为左端点的最优值。
这样的话,处理我们题目中所提到的不能重复的限制就非常简单了。
我们只考虑区间内重复元素最靠近左端点的那一个的贡献。再找出每一个元素前一个与它相同的数的位置 pre。这样,每一个元素作为右端点时,会影响到的左端点的区间范围在 pre + 1 到这个位置。
然后就是维护的问题。我们要实现这样一个数据结构(1)区间加法(2)询问某一个区间内所有元素的历史最大值。
嘛……显然就是线段树。
第一个操作还好说,第二个怎么办呢?如果当更新当前时刻的最大值时去更新历史最大值会不会有问题?直觉上好像会考虑漏掉一些东西。
的确会漏掉。假如在某个时刻 i 我们更新了区间 [l, r] 的历史最大值,在时刻 i + 1 我们更新了 [l, r] 的父亲,给它加上了一个正值。这个时候区间 [l, r] 的历史最大值应该就会加上这个正值,但是我们会打上 tag,并不会更新 [l, r]。然后在时刻 i + 2 我们更新 [l, r] 的父亲时给它加一个负值,此时 [l, r] 的当前时刻最大值就会减小,但是我们所想要的历史时刻最大值,并没有在我们预想的时刻更新,而 tag 就变成了负值。
【以上口胡希望大家还能懂】
因此,我们在线段树中再维护一个东西,表示历史标记的最大值。
就是在某一个时段 [tl, tr] 中,父结点并没有去更新儿子结点而是打上了 tag,此时儿子还停留在时刻 tl 之前的状态。当我们要求儿子结点的历史最大值时如果把当前时刻 tr 的 tag 传下去肯定不行,所以我们就维护从上一次下传 tag 开始,即时段 [tl, tr] 的 tag 最大值。
@accepted code@
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
const ll INF = (1LL<<62);
struct query{
int le, num;
query(int _l=0, int _n=0):le(_l), num(_n){}
};
vector<query>qry[MAXN + 5];
int a[MAXN + 5], bin[2*MAXN + 5], pre[MAXN + 5];
ll ans[MAXN + 5];
struct node{
int le, ri;
ll omax, otag, nmax, ntag;
}tree[4*MAXN + 5];
void tbuild(int x, int l, int r) {
tree[x].le = l, tree[x].ri = r;
tree[x].omax = tree[x].nmax = tree[x].otag = tree[x].ntag = 0;
if( l == r ) return ;
int mid = (l + r) >> 1;
tbuild(x<<1, l, mid);
tbuild(x<<1|1, mid+1, r);
}
void pushdown(int x) {
if( tree[x].otag || tree[x].ntag ) {
tree[x<<1].otag = max(tree[x<<1].otag, tree[x].otag + tree[x<<1].ntag);
tree[x<<1].omax = max(tree[x<<1].omax, tree[x].otag + tree[x<<1].nmax);
tree[x<<1].ntag = tree[x<<1].ntag + tree[x].ntag;
tree[x<<1].nmax = tree[x<<1].nmax + tree[x].ntag;
tree[x<<1|1].otag = max(tree[x<<1|1].otag, tree[x].otag + tree[x<<1|1].ntag);
tree[x<<1|1].omax = max(tree[x<<1|1].omax, tree[x].otag + tree[x<<1|1].nmax);
tree[x<<1|1].ntag = tree[x<<1|1].ntag + tree[x].ntag;
tree[x<<1|1].nmax = tree[x<<1|1].nmax + tree[x].ntag;
tree[x].ntag = tree[x].otag = 0;
}
}
void pushup(int x) {
tree[x].nmax = max(tree[x<<1].nmax, tree[x<<1|1].nmax);
tree[x].omax = max(tree[x<<1].omax, tree[x<<1|1].omax);
}
void tmodify(int x, int l, int r, ll k) {
if( l > tree[x].ri || r < tree[x].le )
return ;
if( l <= tree[x].le && tree[x].ri <= r ) {
tree[x].nmax += k, tree[x].ntag += k;
tree[x].otag = max(tree[x].otag, tree[x].ntag);
tree[x].omax = max(tree[x].omax, tree[x].nmax);
return ;
}
pushdown(x);
tmodify(x<<1, l, r, k);
tmodify(x<<1|1, l, r, k);
pushup(x);
}
ll tquery(int x, int l, int r) {
if( l > tree[x].ri || r < tree[x].le )
return -INF;
if( l <= tree[x].le && tree[x].ri <= r )
return tree[x].omax;
pushdown(x);
return max(tquery(x<<1, l, r), tquery(x<<1|1, l, r));
}
int main() {
int N, Q; scanf("%d", &N);
for(int i=1;i<=N;i++) {
scanf("%d", &a[i]);
pre[i] = bin[a[i] + MAXN];
bin[a[i] + MAXN] = i;
}
scanf("%d", &Q);
for(int i=1;i<=Q;i++) {
int l, r;
scanf("%d%d", &l, &r);
qry[r].push_back(query(l, i));
}
tbuild(1, 1, N);
for(int i=1;i<=N;i++) {
tmodify(1, pre[i] + 1, i, a[i]);
for(int j=0;j<qry[i].size();j++)
ans[qry[i][j].num] = tquery(1, qry[i][j].le, i);
}
for(int i=1;i<=Q;i++)
printf("%lld\n", ans[i]);
}
@details@
题目所涉及两个套路非常常见。
因为我们遇到子区间常用方法是从线段树的中点将两段信息拼在一起。
遇到历史版本最大值常用方法应该是什么可持久化数据结构。