UVALive ~ 3938 ~ Ray, Pass me the dishes!(线段树 动态区间最大和)

本文探讨了区间最大和子序列问题,通过构建线段树并维护最大连续和、最大前缀和及最大后缀和,实现了高效查询。介绍了算法的具体实现细节与代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给出一个长度为n的整数序列D,你的任务是对m个询问做出回答。对于询问(a,b),需要找到两个下标x和y,使得a≤x≤y≤b,并且D[x]+D[x+1]+...+D[y]尽量大。如果有多组满足条件的x和y,x应该尽量小。如果还有多解,y应该尽量小。

【输入格式】

输入包含多组数据。每组数据第一行为两个整数n和m(1≤n,m≤500000),即整数序列的长度和查询个数。第二行包含n个整数,一次为D1,D2,...Dn的值。这些整数的绝对值均不超过10^9。以下m行每行为一个查询,包含两个整数a和b。输入结束标志位文件结束符EOF。

【输入格式】

对于每组数据,输出数据编号,然后为每个查询输出一行,包含两个整数x和y。

【分析】

本题看上去很像RMQ 问题,但稍微琢磨一下就会发现本题和RMQ的重要区别: 整个区间的解不能简单地通过各个子区间的解合并得到(想一想,为什么),所以Sparse-Table算法中的预处理和查询部分均不适用于本题。

《算法竞赛入经典》中讨论过本题的静态版本,即最大连续和子序列,且介绍过它的分治算法: 最优解要么完全在左半序列,要么完全在右半序列,要么跨越中点。由于线性算法的存在,这个算法可能并没有引起很多读者的重视,但这个思路却是解决本题的关键。

构造一棵线段树,其中每个结点维护3 个值: 最大连续和max_sub、最大前缀和max_prefix 与最大后缀和max_suffix。具体来说,max_sub(a,b)是满足a≤x≤y≤b 且D[x]+D[x+1]+...+D[y]最大的二元组(x,y); max_prefix(a,b)是满足a≤x≤b且D[a]+D[a+1]+...+D[x]最大的整数x。max_suffix(a,b)是满足a≤x≤b且D[x]+D[x+1]+...+D[b]最大的整数x。

比如,询问(20,50),则线段[20,50]在线段树的根节点处被分成了两条线段[20,32]和[33,35]。则max_sub(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_suffix(20,32),max_prefix(33,50))。(想一想,为什么)。

类似地,max_prefix和max_suffix也可以这样递推。建树时间复杂度为O(n),单组查询的时间复杂度为O(logN)。

上述内容来自《算法竞赛入门经典-训练指南》


AC代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 500000 + 5;
const int MaxNode = MAXN * 2;
typedef long long LL;
typedef pair<int, int> Interval;
LL prefix_sum[MAXN];//前缀和
LL sum(int L, int R){ return prefix_sum[R] - prefix_sum[L-1]; }
LL sum(Interval x) { return sum(x.first, x.second); }
Interval better(Interval a, Interval b)
{
     if(sum(a) != sum(b)) return sum(a) > sum(b) ?  a : b;
     return a < b ? a : b; // 利用pair自带的字典序
}
struct IntervalTree
{
    int max_prefix[MaxNode], max_suffix[MaxNode], qL, qR;
    Interval max_sub[MaxNode];

    void build(int o, int L, int R)
    {
        if (L == R)
        {
            max_prefix[o] = max_suffix[o] = L;
            max_sub[o] = make_pair(L, L);
            return ;
        }
        int M = L + (R-L)/2;
        int lc = o*2, rc = o*2+1;
        build(lc, L, M);
        build(rc, M+1, R);
        //递推max_prefix
        LL v1 = sum(L, max_prefix[lc]);
        LL v2 = sum(L, max_prefix[rc]);
        max_prefix[o] = v1 >= v2 ? max_prefix[lc] : max_prefix[rc];
        //递推max_suffix
        v1 = sum(max_suffix[lc], R);
        v2 = sum(max_suffix[rc], R);
        max_suffix[o] = v1 >= v2 ? max_suffix[lc] : max_suffix[rc];
        //递推max_sub
        max_sub[o] = better(max_sub[lc], max_sub[rc]);//完全在左子树或者右子树
        max_sub[o] = better(max_sub[o], make_pair(max_suffix[lc], max_prefix[rc]));//跨过中线
    }

    Interval query_prefix(int o, int L , int R)
    {
        if (max_prefix[o] <= qR) return make_pair(L, max_prefix[o]);
        int M = L + (R-L)/2;
        int lc = o*2, rc = o*2+1;
        if (qR <= M) return query_prefix(lc, L, M);
        Interval i = query_prefix(rc, M+1, R);
        i.first = L;
        return better(i, make_pair(L, max_prefix[lc]));
    }

    Interval query_suffix(int o, int L , int R)
    {
        if (max_suffix[o] >= qL) return make_pair(max_suffix[o], R);
        int M = L + (R-L)/2;
        int lc = o*2, rc = o*2+1;
        if (qL > M) return query_suffix(rc, M+1, R);
        Interval i = query_suffix(lc, L, M);
        i.second = R;
        return better(i, make_pair(max_suffix[rc], R));
    }

    Interval query(int o, int L , int R)
    {
        if (qL <= L && R <= qR) return max_sub[o];
        int M = L + (R-L)/2;
        int lc = o*2, rc = o*2+1;
        if (qR <= M) return query(lc, L, M);
        if (qL > M) return query(rc, M+1, R);
        Interval x1 = query_prefix(rc, M+1, R);//右半的前缀
        Interval x2 = query_suffix(lc, L, M);//左半的后缀
        Interval x3 = better(query(lc, L, M), query(rc, M+1, R));//完全在左子树或右子树
        return better(x3, make_pair(x2.first, x1.second));//跨过中线
    }
};

IntervalTree tree;
int main()
{
    int CASE = 1, n, Q;
    while (~scanf("%d%d", &n, &Q))
    {
        prefix_sum[0] = 0;
        for (int i = 1; i <= n; i++)
        {
            int t; scanf("%d", &t);
            prefix_sum[i] = prefix_sum[i-1] + t;
        }
        tree.build(1, 1, n);
        printf("Case %d:\n", CASE++);
        while (Q--)
        {
            scanf("%d%d", &tree.qL, &tree.qR);
            Interval ans = tree.query(1, 1, n);
            printf("%d %d\n", ans.first, ans.second);
        }
    }
    return 0;
}
/*
3 1
1 2 3
1 1
*/



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值