描述
小Ho得到了一个数组作为他的新年礼物,他非常喜欢这个数组!
在仔细研究了几天之后,小Ho成功的将这个数组拆成了若干段,并且每段的和都不为0!
现在小Ho希望知道,这样的拆分方法一共有多少种?
两种拆分方法被视作不同,当且仅当数组断开的所有位置组成的集合不同。
输入
每组输入的第一行为一个正整数N,表示这个数组的长度
第二行为N个整数A1~AN,描述小Ho收到的这个数组
对于40%的数据,满足1<=N<=10
对于100%的数据,满足1<=N<=, |Ai|<=100
输出
对于每组输入,输出一行Ans,表示拆分方案的数量除以(+7)的余数。
样例输入
5
1 -1 0 2 -2
样例输出
5
分析:
这个题有点像石子合并,我们尝试用动归的方法来做,那么首先我们要定义问题解的结构,我们假设dp[i]表示i个数拆分方法的种数。我们假设拆分的最后一段分别从位置j(j = 0, 1, ...)开始,sum[j, i]表示数组下标j到i的和,那么有:
我们可以用O()的方式来得到N在规模较小时的答案,对于该问题来讲,则需要进一步优化。我们可以设置一个变量pre来记录
,当计算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;
}