hihocoder1475 数组分拆【DP+前缀和优化】

DP算法求解序列求和问题
本文介绍了一种使用动态规划(DP)算法解决特定序列求和问题的方法。通过树状数组或map来高效地更新和查询前缀和的方案数,确保了算法在大数据集下的可行性。

思路:

DP[ i ] 代表以 i 结尾的方案数. dp[i] += sum[i] - sum[j - 1] != 0 ? dp[j] : 0 ;

对于100%的数据,满足1<=N<=105, |Ai|<=100
n 1e5呀,两层for,GG;
利用树状数组维护sum[i],存的是以sum[i]的方案数 ,
那么每次加上当前所有的方案,减去sum[i]的方案,就好了。
这里还有一个小问题就是 |sum[i]|<=1e7,所以先离散化一下就好了。
把当前所有前缀和的方案数加起来。。然后减掉以自己的方案数。

也可以用map.


树状数组维护。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
const int N=1e5+10;
LL sval[N],sum[N];
LL dp[N];
int lowbit(int x)
{
    return x&(-x);
}
LL Sum(int d)
{
    LL ans=0;
    while(d)
    {
        ans=(ans+sval[d])%mod;
        d=d-lowbit(d);
    }
    return ans;
}

void add(int d,LL val,int n)
{
    while(d<=n)
    {
        sval[d]=(sval[d]+val)%mod;
        d+=lowbit(d);
    }
}

vector<LL>xs;
int main()
{
    LL n,a;
    scanf("%lld",&n);
    sum[0]=0;
    xs.push_back(0);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a);
        sum[i]=sum[i-1]+a;
        xs.push_back(sum[i]);
    }
    sort(xs.begin(),xs.end());
    auto it=unique(xs.begin(),xs.end());
    for(int i=0;i<=n;i++)
        sum[i]=lower_bound(xs.begin(),it,sum[i])-xs.begin()+1;
    memset(dp,0,sizeof(dp));
    for(int i=0;i<=n;i++)
    {
        dp[i]+=Sum(xs.size());
        dp[i]-=Sum(sum[i])-Sum(sum[i]-1);
        dp[i]=(dp[i]+mod)%mod;
        if(dp[i]==0)
            dp[i]=1;
        add(sum[i],dp[i],xs.size());
    }
    printf("%lld\n",dp[n]);
    return 0;
}


map维护

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod=1e9+7;
const int N=1e5+10;
LL sum[N];
LL dp[N];
map<int,int>mp;

int main()
{
    LL n,a;
    scanf("%lld",&n);
    sum[0]=0;
    mp.clear();
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a);
        sum[i]=sum[i-1]+a;
    }
    memset(dp,0,sizeof(dp));
    dp[0]=1;
    mp[0]=1;
    LL cnt=1;
    for(int i=1;i<=n;i++)
    {
        if(!mp.count(sum[i]))
        {
            dp[i]=cnt;
            mp[sum[i]]=dp[i];
        }
        else
        {
            dp[i]=((cnt-mp[sum[i]])%mod+mod)%mod;
            mp[sum[i]]=(mp[sum[i]]+dp[i]+mod)%mod;
        }
        cnt=(cnt+dp[i]+mod)%mod;
    }
    printf("%lld\n",dp[n]);
    return 0;
}



转载于:https://www.cnblogs.com/keyboarder-zsq/p/6777389.html

<think>好的,我现在需要解释为什么在这段Java代码中,数组的长度被设置为N+1,比如t、d、l、st数组都是new int[N + 1]或者boolean[N + 1]。这个问题看起来是关于数组索引从1开始还是从0开始的常见问题。用户可能对数组长度的设定有疑问,尤其是为什么不是直接用N而是N+1。 首先,我应该回忆Java数组的基本知识。Java数组的索引是从0开始的,所以通常声明数组时,如果需要有N个元素,数组长度是N,索引从0到N-1。但在这段代码中,数组被声明为N+1的长度,比如t数组是int[N + 1],然后循环是从i=1到i<=N,也就是使用了索引1到N。这意味着数组的有效数据是从1开始存储的,而索引0的位置可能未被使用或者被忽略。 接下来,我需要分析为什么这样做。可能的原因有几个:一是程序员习惯使用1-based索引,比如在算法题中,有时为了方便,尤其是当问题描述中的输入数据从1开始编号时,使用1-based数组可以避免转换;二是可能在某些情况下,索引0有特殊的用途,比如作为哨兵值或其他用途,但根据代码中的初始化部分来看,数组初始化后,循环从i=1开始填充数据,所以索引0的位置可能未被使用;三是可能代码的后续部分需要处理到N个元素,而使用1到N的索引更直观,比如在某些动态规划问题中,0可能表示初始状态,但这里的情况可能更简单。 然后,我需要考虑这样做的优缺点。优点可能是代码的可读性更好,尤其是当输入的飞机编号是从1到N时,数组的索引直接对应飞机的编号,不需要调整。缺点则是浪费了一个数组元素的空间(索引0的位置未被使用),这在N很大时可能会有内存问题,但通常情况下,这种开销可以忽略不计。 用户的问题直接是为什么数组长度要是N+1,而他们的示例代码中确实使用了N+1的长度。因此,我需要明确说明,这是因为数组的索引从1开始到N,所以需要N个元素,而数组的长度必须是N+1才能包含索引0到N,其中索引1到N被使用,而索引0未被使用。 可能用户没有考虑到数组索引从0开始的问题,所以需要解释清楚Java数组的索引机制,以及为什么声明N+1长度才能让索引1到N有效。同时,可能需要指出这样做的好处,比如避免在循环中进行i-1的转换,提高代码可读性,尤其是在处理与问题描述中编号对应的数据时。此外,如果有后续的代码逻辑需要从1开始访问数组,比如可能与某种算法逻辑相关,比如动态规划中的状态定义,但根据当前代码片段,可能只是简单的数据存储。 另外,需要检查是否存在其他可能的原因,比如多线程或并发处理中的特殊需求,但根据当前代码片段,这似乎是一个单线程的输入处理,所以可能性较低。因此,主要原因是程序员选择使用1-based索引来匹配问题中的编号,从而声明数组长度为N+1,以便索引1到N有效,而索引0未被使用。 总结来说,用户的问题的答案是因为数组的索引被设计为从1开始到N,因此需要长度为N+1的数组,这样才能合法地访问到索引N的位置,而Java数组的索引是从0开始的,长度为N+1允许索引从0到N,但实际只使用1到N的部分。这样做通常是为了让索引与问题中的编号(如第1架到第N架飞机)直接对应,简化代码逻辑,避免在访问数组时频繁地进行索引调整。</think>在Java代码中,数组的索引默认从 **0** 开始。如果代码中声明数组的长度为 **`N + 1`**,通常是因为程序员希望**将数据的存储位置从索引 `1` 开始使用**,而索引 `0` 可能被忽略或留作特殊用途。以下是具体原因和解释: --- ### **为什么数组长度是 `N + 1`?** 1. **索引对齐问题** - 如果输入的飞机编号是 `1` 到 `N`(而非 `0` 到 `N-1`),使用索引 `1` 到 `N` 存储数据可以**直接让数组下标与飞机编号一一对应**。例如: ```java // 假设输入的第 i 架飞机对应数组的 t[i], d[i], l[i] for (int i = 1; i <= N; i++) { t[i] = sc.nextInt(); // 第 i 架飞机的到达时间 d[i] = sc.nextInt(); // 第 i 架飞机的盘旋时间 // ... } ``` - 这样设计能**避免索引转换**(如 `i-1`),使代码逻辑更直观。 2. **动态规划或递归问题中的常见设计** - 在某些算法场景(如动态规划),索引 `0` 可能被用作**边界条件或初始状态**。例如: ```java dp[0] = 0; // 初始化一个虚拟状态 for (int i = 1; i <= N; i++) { dp[i] = ...; // 基于 dp[i-1] 计算当前状态 } ``` - 虽然当前代码片段没有明确使用 `0` 索引,但预留 `0` 的位置可能是为了后续逻辑的扩展。 3. **代码可读性** - 如果问题描述中的飞机编号是 `1` 到 `N`,使用 `1` 到 `N` 的索引会让代码更贴近自然语言描述,降低理解成本。 --- ### **为什么不直接用 `N`?** - 如果数组长度为 `N`,则有效索引是 `0` 到 `N-1`。如果强行用 `1` 到 `N` 的索引访问数组,会导致 **`ArrayIndexOutOfBoundsException`** 异常。 - 例如:当 `N=3` 时,数组长度为 `3`,索引范围是 `0,1,2`。若尝试访问 `t[3]`,会直接越界。 --- ### **示例说明** 假设 `N=3`(3架飞机): - 如果数组声明为 `int[N+1]`(即长度4),则索引为 `0,1,2,3`。 - 输入时使用索引 `1,2,3` 存储数据,而索引 `0` 未使用: ```java t[1] = 飞机1的到达时间 t[2] = 飞机2的到达时间 t[3] = 飞机3的到达时间 ``` --- ### **总结** - **`N + 1` 的长度**是为了让数组索引从 `1` 开始使用,直接对齐输入数据的编号(如第1架到第N架)。 - 这种设计在算法题中非常常见,尤其是当问题描述的实体编号从 `1` 开始时。 - 虽然会浪费索引 `0` 的空间,但通常影响可忽略不计,且能显著提升代码可读性和逻辑简洁性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值