[HZOI] 山海经 题解

0.题目大意

给出一个序列,每次查询一个区间的最大子段和的端点和值。序列长度 \(n \le 10^{5}\)

1.思路

显然应该使用线段树。题目要求每次求一个区间的最大子段和,那么在线段树节点中应该维护这个节点的最大子段和。然而,只维护最大子段和是无法从子节点合并出父节点的。

考虑一个节点,它的最大子段和可能有以下几个来源:

|_____________________________|               <-节点
   |_________|                                               <-最大子段和
|_______________ |_____________|               <-左右子节点
|_____________________________|               <-节点
                   |_________|                  <-最大子段和
|_______________ |_____________|               <-左右子节点
|_____________________________|               <-节点
        |_________________|                            <-最大子段和
|_______________ |_____________|               <-左右子节点

情况1、2比较容易处理(直接照搬子节点的最大子段和),下面主要考虑情况3(最大子段和跨过中点)。

注意到可以把情况3拆分成这样:

|_____________________________|               <-节点
    |__________| |_____|                            <-最大子段和
|______________| |_____________|               <-左右子节点

即拆分成左子节点的后缀和右子节点的前缀,所以考虑维护每个节点的 最大前缀和最大后缀和 ,情况3就可以表示为左子节点的最大后缀和和右子节点的最大前缀和的并,然后就可以愉快地合并了。

解决了如何合并的问题,剩下的几乎就是模板了

2.坑点与吐槽

  1. 题目要求按照字典序输出,这莫名其妙地增加了许多难度
  2. 需要输出端点,这莫名其妙地增加了许多要维护的变量,又莫名其妙地增加了许多难度
  3. 综上所述,出题人是一个大毒瘤,建议枪毙

3.Code

#include <bits/stdc++.h>
using namespace std;
#define MAXN 100000
#define INF 0x3fffffff
struct answ{
    int i,j,k;
    answ(){i=j=k=0;}
    answ(int _i,int _j,int _k){i=_i;j=_j;k=_k;}
};
struct segment_tree{
    int lp,rp;
    segment_tree *ls,*rs;
    int sum,max_qz_sum,max_qz_sum_rp,max_hz_sum,max_hz_sum_lp,max_sum,max_sum_lp,max_sum_rp;
    segment_tree(int a[],int l,int r){
        lp=l;
        rp=r;
        if(l==r){
            ls=rs=NULL;
            sum=max_qz_sum=max_hz_sum=max_sum=a[l];
            max_qz_sum_rp=max_hz_sum_lp=max_sum_lp=max_sum_rp=l;
        }else{
            int mid=(l+r)/2;
            ls=new segment_tree(a,l,mid);
            rs=new segment_tree(a,mid+1,r);
            sum=ls->sum+rs->sum;
            if(ls->max_qz_sum >= ls->sum+rs->max_qz_sum){//情况1
                max_qz_sum=ls->max_qz_sum;
                max_qz_sum_rp=ls->max_qz_sum_rp;
            }else{
                max_qz_sum=ls->sum+rs->max_qz_sum;
                max_qz_sum_rp=rs->max_qz_sum_rp;
            }
            if(rs->max_hz_sum >= rs->sum+ls->max_hz_sum){//情况2
                max_hz_sum=rs->max_hz_sum;
                max_hz_sum_lp=rs->max_hz_sum_lp;
            }else{
                max_hz_sum=rs->sum+ls->max_hz_sum;
                max_hz_sum_lp=ls->max_hz_sum_lp;
            }
            if(ls->max_sum >= rs->max_sum){//情况3
                max_sum=ls->max_sum;
                max_sum_lp=ls->max_sum_lp;
                max_sum_rp=ls->max_sum_rp;
            }else{
                max_sum=rs->max_sum;
                max_sum_lp=rs->max_sum_lp;
                max_sum_rp=rs->max_sum_rp;
            }
            if(max_sum<ls->max_hz_sum+rs->max_qz_sum){
                max_sum=ls->max_hz_sum+rs->max_qz_sum;
                max_sum_lp=ls->max_hz_sum_lp;
                max_sum_rp=rs->max_qz_sum_rp;
            }else if(max_sum==ls->max_hz_sum+rs->max_qz_sum){
                if(max_sum_lp>ls->max_hz_sum_lp){
                    max_sum=ls->max_hz_sum+rs->max_qz_sum;
                    max_sum_lp=ls->max_hz_sum_lp;
                    max_sum_rp=rs->max_qz_sum_rp;
                }else if(max_sum_lp==ls->max_hz_sum_lp){
                    if(max_sum_rp>rs->max_qz_sum_rp){
                        max_sum=ls->max_hz_sum+rs->max_qz_sum;
                        max_sum_lp=ls->max_hz_sum_lp;
                        max_sum_rp=rs->max_qz_sum_rp;
                    }
                }
            }//如您所见,由于愚蠢的“按字典序输出”存在,代码莫名其妙地增加了许多
        }
    }
    answ query_qz(int r){//查询最大前缀和
        if(r>=rp){
            return answ(0,max_qz_sum_rp,max_qz_sum);
        }else{
            answ res=ls->query_qz(r);
            if(r>=rs->lp){
                answ R=rs->query_qz(r);
                if(res.k<R.k+ls->sum){
                    res=answ(0,R.j,R.k+ls->sum);
                }
            }
            return res;
        }
    }
    answ query_hz(int l){//查询最大后缀和
        if(l<=lp){
            return answ(max_hz_sum_lp,0,max_hz_sum);
        }else{
            answ res=rs->query_hz(l);
            if(l<=ls->rp){
                answ L=ls->query_hz(l);
                if(res.k<L.k+rs->sum){
                    res=answ(L.i,0,L.k+rs->sum);
                }
            }
            return res;
        }
    }
    answ query(int l,int r){//查询最大字段和
        if(l<=lp&&rp<=r){
            return answ(max_sum_lp,max_sum_rp,max_sum);
        }else{
            answ res(0,0,-INF),L,R;
            if(l<=ls->rp){
                L=ls->query(l,r);
                if(L.k>res.k){
                    res=L;
                }
            }
            if(r>=rs->lp){
                R=rs->query(l,r);
                if(R.k>res.k){
                    res=R;
                }
            }
            if(l<=ls->rp&&r>=rs->lp){
                L=ls->query_hz(l);
                R=rs->query_qz(r);
                if(L.k+R.k>res.k){
                    res=answ(L.i,R.j,L.k+R.k);
                }
            }
            return res;
        }
    }
};
int n,m,a[MAXN+5],sum[MAXN+5];
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
    }
    segment_tree data(a,1,n);
    for(int i=1;i<=m;i++){
        int l,r;
        cin>>l>>r;
        if(r-l+1>33){//在数据量较小时使用暴力可以提高效率
            answ ans=data.query(l,r);
            cout<<ans.i<<" "<<ans.j<<" "<<ans.k<<endl;
        }else{
            int ansl=0,ansr=0,ans=-INF;
            for(int j=l;j<=r;j++){
                for(int k=j;k<=r;k++){
                    if(ans<sum[k]-sum[j-1]){
                        ansl=j;
                        ansr=k;
                        ans=sum[k]-sum[j-1];
                    }
                }
            }
            cout<<ansl<<" "<<ansr<<" "<<ans<<endl;
        }
    }
    return 0;
}
4.点一个赞罢QwQ
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入数据。输入数据中包含了道路的数量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负数的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值