CF1197D Yet Another Subarray Problem [思维+线段树/dp]

本文解析了一道算法题目,目标是找到一个整数序列中具有最大价值的连续子序列,价值由子序列之和与特定函数计算得出。文章详细介绍了如何通过线段树和动态规划解决此问题,降低时间复杂度。

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

传送门
题意:给一个长度为n的整数序列,定义一段连续子序列的价值为这段连续子序列之和sum(L,R) — k * f(len/m),其中k为给定的整数,len为这段连续子序列的长度,f(x)表示x向上取整,其中m为给定整数,求出连续子序列的最大价值
题解:这题并不难 (虽然我手残地写崩线段树wa了一个多小时还好最后几分钟过了)首先把sum(L,R)转化成sum[R] — sum[L]的前缀和的形式,然后想到对于每个f(len/m),位置 i 作为左端点L时,R的范围在[L+f(len/m)*m,L+(f(len/m)+1)m-1],显然对于每个i作为左端点L时,应该使sum[R]尽可能大,这显然可以用线段树求最大的sum[R],而如果对于每个i作为左端点时,如果枚举f(len/m),那么复杂度就是n * n * logn 显然不可以,而考虑到距离它m的位置i+m作为左端点时的右端点R对i的贡献 sum[R] — sum[i] — k * f(len/m)相当于求max(sum[R] — sum[i] — k * f(len/m),sum[R] — sum[i+m] — k * (f(len/m) — 1),即sum[R] — k * f(len/m) — min(sum[i],sum[i+m] — k),所以可以直接用sum[i]+k更新sum[i+m],就可以把复杂度降为nlogn

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define debug(x) cout<<#x<<" is "<<x<<endl;

const int maxn=3e5+5;
const ll inf=2e18;

ll n,m,k,a[maxn],sum[maxn];

struct Node{
    int l;
    int r;
    ll maxx;
}nod[maxn<<2];

void pushup(int rt){
    nod[rt].maxx=max(nod[rt<<1].maxx,nod[(rt<<1)|1].maxx);
}

void build(int rt,int l,int r){
    nod[rt].l=l;
    nod[rt].r=r;
    if(l==r){
        nod[rt].maxx=sum[l];
        return;
    }
    int mid=(l+r)>>1;
    build(rt<<1,l,mid);
    build((rt<<1)|1,mid+1,r);
    pushup(rt);
}

ll query(int rt,int l,int r,int l0,int r0){
    if(l>=l0&&r<=r0)return nod[rt].maxx;
    int mid=(l+r)>>1;
    ll xx=-inf;
    if(mid>=l0)xx=max(query(rt<<1,l,mid,l0,r0),xx);
    if(mid<r0)xx=max(query((rt<<1)|1,mid+1,r,l0,r0),xx);
    return xx;
}

int main(){
    scanf("%lld%lld%lld",&n,&m,&k);
    for(int i=1;i<=n;i++){scanf("%lld",&a[i]);sum[i]=sum[i-1]+a[i];}
    build(1,1,n);
    ll ans=0;
    for(int i=1;i<=n;i++){
        if(i>=m+1)sum[i-1]=min(sum[i-1],sum[i-m-1]+k);
        ll x=query(1,1,n,i,min(i+m-1,n));
        ans=max(ans,x-sum[i-1]-k);

    }
    printf("%lld\n",ans);
    return 0;
}

由于m很小,也可以用n*m的dp做

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define debug(x) cout<<#x<<" is "<<x<<endl;

const int maxn=3e5+5;
const ll inf=2e18;

ll n,m,k,a[maxn],dp[maxn][11];

int main(){
    scanf("%lld%lld%lld",&n,&m,&k);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    ll ans=0;
    for(int i=0;i<=n;i++){
        for(int j=0;j<=m;j++){
            dp[i][j]=-inf;
        }
        dp[i][0]=0;
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<m;j++){
            if((j-1+m)%m==0){
                if(dp[i-1][(j-1+m)%m]!=-inf)dp[i][j]=max(dp[i][j],dp[i-1][(j-1+m)%m]+a[i]-k);
            }
            else{
                if(dp[i-1][(j-1+m)%m]!=-inf)dp[i][j]=max(dp[i][j],dp[i-1][(j-1+m)%m]+a[i]);
            }
            ans=max(ans,dp[i][j]);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

内容概要:本文详细介绍了900W或1Kw,20V-90V 10A双管正激可调电源充电机的研发过程和技术细节。首先阐述了项目背景,强调了充电机在电动汽车和可再生能源领域的重要地位。接着深入探讨了硬件设计方面,包括PCB设计、磁性器件的选择及其对高功率因数的影响。随后介绍了软件实现,特别是程序代码中关键的保护功能如过流保护的具体实现方法。此外,文中还提到了充电机所具备的各种保护机制,如短路保护、欠压保护、电池反接保护、过流保护和过温度保护,确保设备的安全性和可靠性。通讯功能方面,支持RS232隔离通讯,采用自定义协议实现远程监控和控制。最后讨论了散热设计的重要性,以及为满足量产需求所做的准备工作,包括提供详细的PCB图、程序代码、BOM清单、磁性器件和散热片规格书等源文件。 适合人群:从事电力电子产品研发的技术人员,尤其是关注电动汽车充电解决方案的专业人士。 使用场景及目标:适用于需要高效、可靠充电解决方案的企业和个人开发者,旨在帮助他们快速理解和应用双管正激充电机的设计理念和技术要点,从而加速产品开发进程。 其他说明:本文不仅涵盖了理论知识,还包括具体的工程实践案例,对于想要深入了解充电机内部构造和工作原理的人来说是非常有价值的参考资料。
<think>好的,用户现在问的是数组实现的线段树和结构体实现的线段树哪个更优。首先,我需要回忆一下这两种实现方式的区别以及各自的优缺点。 用户之前的问题是关于如何用线段树解决最大子数组和的问题,当时给出的代码是用结构体实现的,每个节点是一个结构体,包含总和、前缀、后缀和最大子数组等信息。现在用户可能是在实际应用中遇到了选择数据结构的问题,或者在学习过程中想了解不同实现方式的优劣。 首先,我需要明确“数组实现”和“结构体实现”具体指什么。通常,线段树可以用数组或指针链接的结构体(比如对象或结构体数组)来实现。数组实现通常是指用一维数组来模拟树结构,通过索引计算来找到左右子节点。而结构体实现可能是指每个节点是一个结构体实例,可能存储在数组中或者通过指针连接。 接下来,我需要比较两者的优缺点。比如,数组实现的优点是内存连续,访问速度快,可能缓存友好,但缺点是空间可能浪费,尤其是当线段树不是完全二叉树的时候。结构体实现可能更灵活,每个节点可以明确包含左右指针或其他信息,但可能在内存中不连续,访问速度稍慢,但空间利用率更高。 另外,用户提到的“数组实现的线段树”可能是指用数组来存储每个节点的数据,而结构体实现可能将每个节点作为一个结构体对象,可能使用动态内存分配或者预先分配的数组来存储这些结构体。 需要考虑的因素包括: 1. **空间效率**:数组实现可能需要预先分配较大的空间(通常是4*n的大小),而结构体实现如果是动态分配的话可能更节省空间,但如果是用数组存储结构体,空间利用率可能类似。 2. **代码复杂度**:结构体实现可能更直观,尤其是当每个节点需要存储多个字段时,比如之前的例子中的total、max_prefix等。用数组实现的话,可能需要多个数组来分别存储这些字段,或者将每个节点的数据打包成一个结构体数组,这时候结构体数组和单独的结构体实例其实差别不大。 可能用户这里的问题更偏向于实现方式的选择,比如是否用数组来紧凑存储节点数据,或者用结构体来组织每个节点的信息。 比如,在之前的代码中,使用的是结构体数组的方式,每个节点是结构体,存储多个字段。而另一种数组实现可能是指将每个字段分开存储,比如四个单独的数组:total[], max_prefix[], max_suffix[], max_subarray[],这样每个节点的数据分布在不同的数组中,可能影响缓存性能,但可能在特定情况下有优势。 或者,用户可能把用数组模拟树结构(比如用2*i+1和2*i+2找子节点)称为数组实现,而结构体实现可能是指用对象或结构体,每个节点包含左右子节点的指针或索引。 不过,在C++中,通常线段树的实现都是用数组来模拟树结构,因为动态分配节点的方式(比如用指针)会导致较高的内存开销和碎片。因此,可能这里的“结构体实现”指的是将每个节点的数据打包成一个结构体,然后用数组来存储这些结构体,这样每个节点在数组中连续存储,而字段在结构体内。 比如,在之前的代码示例中,线段树是用一个vector<Node> tree来存储,每个Node是一个结构体,包含四个字段。这样,每个节点的数据是连续存储的,结构体的方式使得每个节点的数据聚合在一起,可能缓存更友好,因为访问一个节点时,所有相关字段都在同一块内存区域。 而另一种数组实现可能是指将每个字段分开存储,比如四个不同的数组,这样访问一个节点的各个字段可能需要访问不同的内存区域,可能导致缓存不命中。 因此,结构体实现(结构体数组)可能在缓存性能上更优,因为同一节点的各个字段在内存中是相邻的,而分开的数组实现可能导致字段分散,降低缓存利用率。 此外,代码的可读性和维护性方面,结构体实现更清晰,每个节点的数据组织明确,而多个数组的方式可能让代码更难维护,尤其是在节点需要新增字段时。 不过,在某些情况下,如果某些字段不需要频繁访问,分开存储可能节省内存,比如如果某些字段在查询过程中不被使用,可以按需加载,但这种情况较少见。 另外,考虑线段树的空间分配。数组实现的线段树通常需要预先分配足够的空间,比如4*n的大小,这在n较大时可能占用较多内存,尤其是在某些情况下线段树不需要完全填满的情况下。而结构体实现如果是动态分配节点的话,可能更节省空间,但动态分配通常效率较低,管理复杂。 不过,在之前的代码示例中,结构体实际上是存储在预分配的数组中(vector<Node> tree),因此本质上还是数组实现,只不过每个数组元素是一个结构体,包含多个字段。因此,这里的结构体实现其实属于数组实现的一种形式,而不是传统的指针式结构体树。 可能用户的问题需要更明确的定义,但根据常见的实现方式,结构体数组和多个字段数组的实现之间的比较,结构体数组在缓存和代码结构上更优。 因此,总结两者的优缺点: 数组实现(多数组): - 优点:可能在某些特定情况下更灵活,比如只需要部分字段时可以单独处理。 - 缺点:缓存不友好,代码维护困难,内存占用可能更高。 结构体实现(结构体数组): - 优点:内存局部性好,代码结构清晰,易于维护。 - 缺点:如果节点中某些字段在操作中不需要,可能会有轻微的内存浪费,但通常可以忽略。 因此,结构体实现的线段树通常更优,尤其是在需要维护多个字段的情况下,如最大子数组和问题中的每个节点需要四个字段。结构体将相关数据聚合在一起,提高缓存效率,简化代码逻辑。 另外,用户可能在选择实现方式时需要考虑编程语言的特点。例如,在C++中,结构体数组的内存布局是连续的,访问效率高,而在其他语言中可能有所不同。 最后,需要确认用户的问题是否涉及动态与静态分配的考量,或者是否有其他特定需求。根据常见的实践,结构体数组(即用数组存储结构体节点)是更优的选择。</think>### 数组实现 vs 结构体实现线段树的对比分析 线段树的实现方式中,"数组实现"和"结构体实现"本质上是**数据组织形式的差异**,而非算法逻辑的不同。以下是两者的核心对比: --- #### 1. **数组实现(多数组存储字段)** - **方法**:用多个独立数组分别存储每个节点的字段(如 `total[]`, `max_prefix[]`, `max_suffix[]`, `max_subarray[]`)。 - **优点**: - **灵活性高**:可单独操作某个字段(例如只更新 `max_prefix`)。 - **内存对齐优化**:若某些字段访问频率差异大,可能优化内存布局。 - **缺点**: - **缓存不友好**:访问一个节点的不同字段需多次内存寻址,降低效率。 - **代码冗余**:需维护多个数组,代码可读性和维护性差。 --- #### 2. **结构体实现(结构体数组)** - **方法**:将每个节点的所有字段封装为结构体,用结构体数组存储(如 `Node tree[]`)。 - **优点**: - **缓存友好**:同一节点的字段在内存中连续存储,减少缓存未命中。 - **代码简洁**:字段集中管理,逻辑清晰(如合并子节点时直接操作结构体)。 - **扩展性强**:新增字段只需修改结构体定义。 - **缺点**: - **轻微内存浪费**:若某些字段使用频率低,可能占用冗余空间(通常可忽略)。 --- ### 性能对比(以最大子数组和问题为例) | 指标 | 结构体实现 | 数组实现 | |---------------------|--------------------------|-----------------------| | **内存局部性** | ⭐⭐⭐⭐⭐(字段连续) | ⭐⭐(字段分散) | | **代码可读性** | ⭐⭐⭐⭐⭐(逻辑集中) | ⭐⭐(多数组分散) | | **扩展性** | ⭐⭐⭐⭐⭐(修改结构体) | ⭐(需修改多个数组) | | **内存占用** | ⭐⭐⭐(结构体对齐) | ⭐⭐⭐⭐(独立分配) | | **适用场景** | 多字段、高频访问 | 单字段优化、特殊需求 | --- ### 结论 - **优先选择结构体实现**:在绝大多数场景(如维护多字段的线段树)中,结构体数组凭借**缓存友好性**和**代码可维护性**显著优于多数组实现。 - **数组实现的特殊用途**:仅当需要极致优化某一字段的内存布局(如 GPU 计算)时,可考虑多数组实现。 --- ### 代码示例对比 #### 结构体实现(推荐) ```cpp struct Node { int total, max_prefix, max_suffix, max_subarray; Node(int val) : /* 初始化字段 */ {} }; vector<Node> tree; // 结构体数组 ``` #### 数组实现(不推荐) ```cpp vector<int> total; // 总和数组 vector<int> max_prefix; // 前缀数组 vector<int> max_suffix; // 后缀数组 vector<int> max_subarray; // 子数组数组 ``` --- ### 总结建议 **除非有明确的性能调优需求(如特定硬件优化),否则应始终优先使用结构体实现**。它在代码可读性、维护性和运行效率之间达到最佳平衡。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值