hiho一下 第233周 数组分拆

题面 :

Link

题意:

将一个数组拆成n个不为0的小段,问拆分的方法数。

思路:

开始很容易直接想到一个O(n^2) 的动态规划。
令dp[i]表示i个数不同种划分数,sum[j,i]表示数组下标从j到i的和。
从划分思想,比较容易想到:

dp[i]=∑dp[j] (j<i&&sum[j,i]!=0)

其中sum[j,i]可以由前缀数组O(1)的得到,具体见下面代码。

但明显这题N<1e5,复杂度不够。
继续优化。
对于dp[i]求和这一步我们同样用前缀数组优化。
定义: dp_sum[i] = ∑ dp[i]

那么对于dp[i] = dp_sum[i-1] - 数量(∑ sum[j+1,i]=0)

对于sum[j+1,i]=0,我们两边同时加上sum[0,j],就变成:sum[0,i] = sum[0,j]。问题变成减去出现sum[0,i] = sum[0,j]的数量,也就是减去前缀和为sum[0,i]的数量。
``于是我们只需要一个Hash表去保存前缀和sum[0,i]出现的个数。时间复杂度O(1)求出sum[0,i]的数量。

则转移方程:dp[i] = dp_sum[i-1] - Hash[sum[0,i]],具体见代码。

代码

O(n^2) TLE代码:

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

int main(){
     int n;
     while(~scanf("%d",&n)){
        memset(dp,0,sizeof(dp));
        memset(sum,0,sizeof(sum));
        sum[0] = 0;
        for(int i=1;i<=n;i++){
          int x;scanf("%d",&x);
          sum[i] = sum[i-1] + x;
        }

        dp[0] = 1;
        for(int i=1;i<=n;i++){
            for(int j=0;j<i;j++){
               if(sum[i]-sum[j]!=0){
                 dp[i]+=dp[j];
               }
            }
            //cout<<dp[i]<<" ";
        }
        //cout<<endl;
        printf("%d\n",dp[n]);
     }
}

O(n)正解代码:

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

map<ll,ll> Hash;

int main(){
     int n;
     while(~scanf("%d",&n)){
        memset(dp,0,sizeof(dp));
        sum[0] = 0;
        for(int i=1;i<=n;i++){
          int x;scanf("%d",&x);
          sum[i] = sum[i-1] + x;
        }

        dp[0] = 1;
        sum_dp[0] = 1;
        Hash[0] = 1;
        for(int i=1;i<=n;i++){
           dp[i] = (sum_dp[i-1] - Hash[sum[i]] + mod)%mod;
           sum_dp[i] = (sum_dp[i-1] + dp[i] + mod)%mod;
           Hash[sum[i]] = (Hash[sum[i]] +dp[i] + mod)%mod;
           //cout<<dp[i]<<" ";
        }
        //cout<<endl;
        printf("%lld\n",dp[n]);
     }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值