数组分拆

描述
小Ho得到了一个数组作为他的新年礼物,他非常喜欢这个数组!

在仔细研究了几天之后,小Ho成功的将这个数组拆成了若干段,并且每段的和都不为0!

现在小Ho希望知道,这样的拆分方法一共有多少种?

两种拆分方法被视作不同,当且仅当数组断开的所有位置组成的集合不同。

输入
每组输入的第一行为一个正整数N,表示这个数组的长度

第二行为N个整数A1~AN,描述小Ho收到的这个数组

对于40%的数据,满足1<=N<=10

对于100%的数据,满足1<=N<=10^5, |Ai|<=100

输出
对于每组输入,输出一行Ans,表示拆分方案的数量除以(10^9+7)的余数。

样例输入
5
1 -1 0 2 -2
样例输出
5
 

分析:

       这个题有点像石子合并,我们尝试用动归的方法来做,那么首先我们要定义问题解的结构,我们假设dp[i]表示i个数拆分方法的种数。我们假设拆分的最后一段分别从位置j(j = 0, 1, ...)开始,sum[j, i]表示数组下标j到i的和,那么有:

dp[i] = \sum_{j = 0}^{i - 1}dp[j] (j < i \ \&\&\ sum[j+1, i]\ !=0)

       我们可以用O(N^2)的方式来得到N在规模较小时的答案,对于该问题来讲,则需要进一步优化。我们可以设置一个变量pre来记录\sum_{0}^{i - 1} dp[k],当计算dp[i]的时候,我们先加上pre,这样就存在多加的情况,是哪些多加了呢?就是sum[j + 1, i] == 0的情况下,将它们对应的dp[j]多加了进来。那么我们需要将其减去。首先我们在sum[j + 1, i] == 0两边加上sum[1, j],则变成sum[1, i] == sum[1, j]。我们用哈希表来存储sum[1, i] == sum[1, j]时,dp[k]的和。 Hash[sum]表示当前情况下sum[1, i](即sum[1, j])对应dp[k](k < i)的和。

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
const int p = 1e9 + 7;
ll pre, sum;
ll dp[N];
unordered_map<ll, ll> Hash;

int main(){
	int N, x;
	while(cin >> N){
		Hash.clear();
		sum = 0;
		//pre = 1对应数组不做划分的情况,算1种情况 
		pre = 1;
		//第一个数字为0:dp[1] = 1 - 1 = 0
		//第一个数字不为0:dp[1] = 1 - 0 = 1 
		Hash[0] = 1;
		for(int i = 1; i <= N; i++){
			cin >> x;
			sum += x;
			//因为下面的pre = (pre + dp[i]) % p可能导致p变小,而使dp[i]为负数
			//所以在此加上p再取余,将其化为正数 
			dp[i] = (pre - Hash[sum] + p) % p;
			//第二个:Hash[sum](记录的是i之前, sum所对应的dp[k]的和)
			//第一个:Hash[sum](sum[1, i] == sum, 将其对应的dp[i]加进去 
			Hash[sum] = (Hash[sum] + dp[i]) % p;
			//pre记录dp的累加和 
			pre = (pre + dp[i]) % p;
		}
		cout << dp[N] << endl;
	}
    return 0;
} 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值