题目大意:求一段整数序列中最大连续续和的左右端点的坐标。
解题思路:因为需要高效的求出一段的区间和,考虑使用线段树算法,从分治的角度来思考这个问题,这个最大的连续和有可能在一段区间的左半区间也有可能在右半区间也有可能贯穿了两个区间。
也就是说我们需要构造一个线段树,并同时维护四个值,最大前缀和,最大后缀和,最大连续和,区间和
这三个值都存在着与子节点的递推关系,利用线段树自底向上求解。
考虑任意一个非叶子节点(叶子节点所有参数等于自身)
最大前缀和 = max (左子树最大前缀,左子树区间和+右子树最大前缀)
最大后缀和 = max (右子树最大后缀,右子树区间和+左子树最大后缀)
最大连续和 = max (左子树最大连续和,右子树最大连续和,左子树最大后缀+右子树最大前缀)
明确了递推过程,剩下的就是编码实现和细节处理了,本题要特别注意一下符号的处理(>= or >)
AC代码:
#include <iostream>
#include <cstdio>
using namespace std;
#define maxn 500005
typedef long long ll;
int score[maxn];
int a,b;
char ch;
struct NODE
{
ll suf,sub,pre,sum;//max_suffix max_prefix max_sub(最大子区间连续和)
int l,r; // the left pos and the right pos of the max_sub
int sl; //sl->the left pos of max_suffix
int pr; //pr->the right pos of max_prefix
} node[4*maxn];
NODE zero = { -(1ll<<40), -(1ll<<40), -(1ll<<40), 0};//node针对update这种运算的零元
void update(NODE &u, NODE &v1, NODE &v2)//将两个子区间的性质合并向上传递
{
if(v1.pre >= v1.sum + v2.pre){
u.pre = v1.pre; u.pr = v1.pr;
}
else{
u.pre = v1.sum + v2.pre; u.pr = v2.pr;
}
if(v2.suf > v1.suf + v2.sum){// pay attention > or >=
u.suf = v2.suf; u.sl = v2.sl;
}
else{
u.suf = v1.suf + v2.sum; u.sl = v1.sl;
}
if(v1.sub >= v2.sub){ // pay attention > or >=
u.sub = v1.sub; u.l = v1.l; u.r = v1.r;
}
else{
u.sub = v2.sub; u.l = v2.l; u.r = v2.r;
}
if( (u.sub < v1.suf + v2.pre) ||
(u.sub == v1.suf + v2.pre && (u.l>v1.sl || (u.l==v1.sl && u.r>v2.pr)))){
u.sub = v1.suf + v2.pre; u.l = v1.sl; u.r = v2.pr;
}
u.sum = v1.sum + v2.sum;
}
void build(ll root, ll l, ll r)
{
if(l==r){
NODE &u = node[root];
u.l = u.r = u.pr = u.sl = r;
u.sum = u.pre = u.suf = u.sub = score[l];
return;
}
ll mid = (l+r)>>1;
build(root<<1, l, mid);
build(root<<1 | 1 ,mid+1, r);
update(node[root], node[root<<1], node[root<<1 | 1]);
}
NODE query(ll root, ll l, ll r)
{
if(l>b || r<a )//无交集
return zero;
if(a<=l && r<=b)//此区间包含root所管理的区间
return node[root];
int mid = (l+r)>>1;
NODE L = query(root<<1, l, mid);//部分相交
NODE R = query(root<<1 | 1 ,mid+1, r);
NODE ans;
update(ans, L, R);
return ans;
}
int main()
{
int n,m;
int kase = 1;
while(~scanf("%d%d",&n,&m))
{
printf("Case %d:\n",kase++);
for(int i=1; i<=n; i++) scanf("%d",score+i);
build(1,1,n);
for(int i=1; i<=m; i++)
{
scanf("%d%d",&a,&b);
NODE ans = query(1,1,n);
printf("%d %d\n",ans.l,ans.r);
}
}
//cout << "Hello world!" << endl;
return 0;
}
上面的写法需要构造一个NODE元素针对update运算的单位元,当然query函数还有其他的写法,也就是不同的区间比较策略。
NODE query(ll root, ll l, ll r)
{
if(a<=l && r<=b)
return node[root];
int mid = (l+r)>>1;
NODE ans;
if(a <= mid && b > mid){
NODE L = query(root<<1, l, mid); NODE R = query(root<<1 | 1, mid + 1, r);
update(ans, L, R);
return ans;
}
if(b<=mid){
return query(root<<1, l, mid);
}
return query(root<<1 | 1, mid+1, r);
}