uva1400 - "Ray, Pass me the dishes!"

链接

https://vjudge.net/problem/UVA-1400

题解

考虑线段树
我怎样得到一个线段树上某个结点所对应区间的最大连续子段和?
使用分治的思想,可以发现有三种情况:

  • 这个子段完全在左儿子中
  • 这个子段完全在右儿子中
  • 这个子段在左儿子中有一部分,右儿子中还有一部分

前两种好处理,就是相同问题的递归。
第三种如何搞?我可以维护线段树上每个结点强制包含右端点的最大值、强制包含左端点的最大值
解决完这个,似乎离最终的问题近了一些

现在,如何用这些信息合并出答案?

暴力
我直接从线段树上把对应的 l o g n logn logn个区间提取出来,然后 O ( l o g 2 n ) O(log^2n) O(log2n)暴力,这个做法貌似复杂度有点高,我自己没试过,但是说不定可以过

分治
利用线段树这个数据结构
考虑我递归的过程
比如我现在在根结点,我要找 [ l , r ] [l,r] [l,r]中的最大连续子段和
a n s ( l , r ) ans(l,r) ans(l,r)有三种可能:

  • 这个子段完全在左儿子 ( l , m i d ) (l,mid) (l,mid)
  • 这个子段完全在右儿子 ( m i d + 1 , r ) (mid+1,r) (mid+1,r)
  • 这个子段在左儿子中有一部分,右儿子中还有一部分

前两种就是相同问题的递归
第三种怎么解决?
如果强行使用左子树的“强制包含右端点的最大值”和右子树“强制包含左端点的最大值”,会有问题,因为题目对区间有限制
这个时候就要考虑如何搞出“线段树的某个子树受区间 ( l , r ) (l,r) (l,r)限制时,强制包含左(右)端点的最大值”
其实仔细想一想就能想出来,我可以利用我所记录的信息合并,具体细节在代码中都有了
这样处理一次查询的时间复杂度是 O ( l o g n ) O(logn) O(logn),但是常数貌似有点大

代码

#include <bits/stdc++.h>
#define maxn 500010
#define linf (1ll<<60)
using namespace std;
typedef long long ll;
ll a[maxn], s[maxn], ndtot;
struct interval
{
    ll l, r;
    interval(ll l, ll r):l(l),r(r){}
    interval(){}
};
bool operator<(interval I1, interval I2)
{
    ll sum1(s[I1.r]-s[I1.l-1]), sum2(s[I2.r]-s[I2.l-1]);
    if(sum1==sum2)
        return make_pair(I1.l,I1.r) > make_pair(I2.l,I2.r);
    return sum1 < sum2;
}
interval operator+(interval I1, interval I2)
{
    if(I1.r!=I2.l-1)
    {
        printf("Error!\n");
        printf("[%lld,%lld] + [%lld,%lld]\n",I1.l,I1.r,I2.l,I2.r);
    }
    return interval(I1.l,I2.r);
}
struct segtree
{
    ll l, r;
    segtree *lch, *rch;
    interval lmax, rmax, max, sum;
}pool[maxn<<2];
void pushup(segtree *p)
{
    // printf("[%lld,%lld]\n",p->l,p->r);
    p->lmax = max(p->lch->lmax , p->lch->sum+p->rch->lmax);
    p->rmax = max(p->rch->rmax , p->lch->rmax+p->rch->sum);
    p->max = max( max(p->lch->max , p->rch->max) , p->lch->rmax+p->rch->lmax );
    p->sum = p->lch->sum+p->rch->sum;
}
void build(segtree *p, ll l, ll r)
{
    ll mid=l+r>>1;
    p->l=l, p->r=r;
    if(l==r)
    {
        p->lmax = p->rmax = p->max = p->sum = interval(l,l);
        return;
    }
    build(p->lch = pool+ ++ndtot, l, mid);
    build(p->rch = pool+ ++ndtot, mid+1,r);
    pushup(p);
}
interval q_sum(segtree *p, ll l, ll r)
{
    ll mid=p->l+p->r>>1;
    interval ans;
    if(l<=p->l and r>=p->r)return p->sum;
    if(l<=mid)ans=q_sum(p->lch,l,r);
    if(r>mid)
    {
        if(l<=mid)ans=ans+q_sum(p->rch,l,r);
        else ans=q_sum(p->rch,l,r);
    }
    return ans;
}
interval q_lmax(segtree *p, ll l, ll r)
{
    ll mid = p->l+p->r>>1;
    interval ans;
    if(l<=p->l and r>=p->r)return p->lmax;
    if(l<=mid)
    {
        ans=q_lmax(p->lch,l,r);
        if(r>mid)ans=max(ans,q_sum(p->lch,l,r)+q_lmax(p->rch,l,r));
    }
    else
    {
        ans=q_lmax(p->rch,l,r);
    }
    return ans;
}
interval q_rmax(segtree *p, ll l, ll r)
{
    ll mid=p->l+p->r>>1;
    interval ans;
    if(l<=p->l and r>=p->r)return p->rmax;
    if(r>mid)
    {
        ans=q_rmax(p->rch,l,r);
        if(l<=mid)ans=max(ans,q_rmax(p->lch,l,r)+q_sum(p->rch,l,r));
    }
    else
    {
        ans=q_rmax(p->lch,l,r);
    }
    return ans;
}
interval q_max(segtree *p, ll l, ll r)
{
    ll mid=p->l+p->r>>1;
    if(l<=p->l and r>=p->r)return p->max;
    if(l<=mid and r>mid)return max( max( q_max(p->lch,l,r) , q_max(p->rch,l,r) ) , q_rmax(p->lch,l,r)+q_lmax(p->rch,l,r));
    if(r<=mid)return q_max(p->lch,l,r);
    if(l>mid)return q_max(p->rch,l,r);
}
ll read(ll x=0)
{
    ll c, f=1;
    for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
    for(;isdigit(c);c=getchar())x=x*10+c-48;
    return f*x;
}
int main()
{
    ll N, M, l, r, i, kase=0;
    segtree *root;
    while(~scanf("%lld%lld",&N,&M))
    {
        printf("Case %lld:\n",++kase);
        for(i=1;i<=N;i++)a[i]=read();
        for(i=1;i<=N;i++)s[i]=s[i-1]+a[i];
        build(root = pool+(ndtot=1),1,N);
        while(M--)
        {
            l=read(), r=read();
            auto ans=q_max(root,l,r);
            printf("%lld %lld\n",ans.l,ans.r);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值