题目链接:“Ray, Pass me the dishes!”
对于刚接触线段树的我,一开始搞个这种难度的属实有点搞人心态,关键这题设的数据类型就很多,用线段树去维护三个值,然后还有很多细节要去处理,搞了一上午才搞出来,线段树还是做得少,得加把劲啊。
题意
给你一个长度为n的序列D,有m次查询。对于询问(l,r),需要我们找两个下标x和y,使a≤x≤y≤b,并且要求 D x + D x + 1 + . . . + D y {D_x+D_{x+1}+...+D_y} Dx+Dx+1+...+Dy的值尽可能大,如果多组满足,x和y应该尽可能小。
题解
由于这个题是带询问的,所以如果用动态规划解决每次查询的时间复杂度为O(r-l+1),那么总的时间复杂度为O(mn),很明显题目中n,m≤500000,用动·态规划是无法解决的。
此时就需要用一种数据结构来帮助我们解决问题——线段树。
首先我们来分析这个题的分治解法,将这个查询区间分为左右区间两部分,那么最优解无非就三种情况。
- 最优解在左区间
- 最优解在右区间
- 最优解在两个区间都有。
我们可以用线段树,每个结点来维护三个值,最大连续和(max_sub),最大前缀和(max_pre),最大后缀和(max_suf),同时由于本题要找最大和的区间,所以还得维护前缀的l和r,后缀的l和r,最优解的子区间l和r,以及当前区间的l和r。
所以在建树的过程中我们得对每一个值进行赋值处理。其中的细节我不多说,也并不复杂多想想就能想出来。
现在我们举个例子看线段树如何解决问题。n=64,查询区间为[20,50]。
那么查询区间为[20,50]的最优解子区间的左右端点有三种情况。
- 左右端点都在[20,32](线段树的左区间内),那么max_sub(20,50) = max _sub (20,32)。
- 左右端点都在[33,50](线段树的右区间内),那么max_sub(20,50) = max _sub (33,50)。
- 左端点在[20,32]内,右端点在[33,50]内,那么max_sub(20,50) = max_suf(20,32)+max_pre(33,50)。
建树的时间复杂度为O(n),查询时间复杂度为O(logn)。
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<bitset>
#include<cassert>
#include<cctype>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<deque>
#include<iomanip>
#include<list>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<vector>
using namespace std;
//extern "C"{void *__dso_handle=0;}
typedef long long ll;
typedef long double ld;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define lowbit(x) x&-x
const double PI=acos(-1.0);
const double eps=1e-6;
const ll mod=1e9+7;
const int inf=0x3f3f3f3f;
const int maxn=1e6+10;
const int maxm=2e6+10;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n,a[maxn];
struct Tree
{
int l,r;
ll max_sub,max_pre,max_suf,sum;
int sub_l,sub_r,pre_pos,suf_pos;
}tree[maxn];
void push_up(Tree& root,Tree& lchild,Tree& rchild)
{
root.sum = lchild.sum + rchild.sum;
if((lchild.sum+rchild.max_pre)>lchild.max_pre)
{
root.max_pre = (lchild.sum+rchild.max_pre);
root.pre_pos = rchild.pre_pos;
}
else
{
root.max_pre = lchild.max_pre;
root.pre_pos = lchild.pre_pos;
}
if((lchild.max_suf+rchild.sum)>=rchild.max_suf)
{
root.max_suf = (lchild.max_suf+rchild.sum);
root.suf_pos = lchild.suf_pos;
}
else
{
root.max_suf = rchild.max_suf;
root.suf_pos = rchild.suf_pos;
}
root.max_sub = lchild.max_sub;
root.sub_l = lchild.sub_l;
root.sub_r = lchild.sub_r;
if((lchild.max_suf+rchild.max_pre)>root.max_sub)
{
root.max_sub = (lchild.max_suf+rchild.max_pre);
root.sub_l = lchild.suf_pos;
root.sub_r = rchild.pre_pos;
}
if((lchild.max_suf+rchild.max_pre)==root.max_sub && root.sub_l>lchild.suf_pos)
{
root.sub_l = lchild.suf_pos;
root.sub_r = rchild.pre_pos;
}
if(rchild.max_sub>root.max_sub)
{
root.max_sub = rchild.max_sub;
root.sub_l = rchild.sub_l;
root.sub_r = rchild.sub_r;
}
}
void build(int root,int l,int r)
{
tree[root].l=l;
tree[root].r=r;
if(l==r)
{
tree[root].sum = a[l];
tree[root].max_sub=tree[root].max_pre=tree[root].max_suf=a[l];
tree[root].sub_l=tree[root].sub_r=tree[root].pre_pos=tree[root].suf_pos=l;
return;
}
int mid=l+(r-l)/2;
build(root*2, l, mid);
build(root*2+1, mid+1, r);
push_up(tree[root],tree[root*2],tree[root*2+1]);
}
Tree query(int p,int l,int r)
{
if((l==tree[p].l) && (r==tree[p].r)) return tree[p];
int mid=tree[p].l+(tree[p].r-tree[p].l)/2;
if(r<=mid) return query(p*2, l, r);
if(l>mid) return query(p*2+1, l, r);
Tree ans;
Tree a1,a2;
a1=query(p*2, l, mid);
a2=query(p*2+1, mid+1, r);
ans.l = l,ans.r=r;
push_up(ans, a1, a2);
return ans;
}
int main()
{
int n,q;
int cas=0;
while(~scanf("%d%d",&n,&q))
{
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
build(1, 1, n);
printf("Case %d:\n",++cas);
while(q--)
{
int l,r;
scanf("%d%d",&l,&r);
Tree ans=query(1, l, r);
printf("%d %d\n",ans.sub_l,ans.sub_r);
}
}
}