文章目录
- 题目表述
- 动态规划+前缀和
- 哈希Hash
- 总结
题目表述
PIPIOJ1046:http://pipioj.online/problem.php?id=1046PIPI得到了一个数组作为他的新年礼物,他非常喜欢这个数组!
在仔细研究了几天之后,PIPI成功的将这个数组拆成了若干段,并且每段的和都不为0!
现在PIPI希望知道,这样的拆分方法一共有多少种?
两种拆分方法被视作不同,当且仅当数组断开的所有位置组成的集合不同。
多组数据。
每组输入的第一行为一个正整数N,表示这个数组的长度
第二行为N个整数A1~AN,描述PIPI收到的这个数组
对于40%的数据,满足1<=N<=10
对于100%的数据,满足1<=N<=100000, |Ai|<=100
对于每组输入,输出一行Ans,表示拆分方案的数量除以(10^9+7)的余数。
样例输入
5
1 -1 0 2 -2
样例输出
5
动态规划+前缀和
还是老样子,先不看数据规模暴力解出来,再优化。
不难看出这题可以用到 DP动态规划+前缀和。
sum[i]表示前i个数的和,有sum[i]=sum[i-1]+a[i];
设dp[i]表示到下标 i 为止当前可组方案次数,以ai为结尾的分组共有i个为aj,aj+1…ai-1,ai
每个分组有效的前提是该分组之和不为0,即 sum[i]-sum[j-1] != 0
则状态转移方程为
dp[i]=(sum[i]-sum[j-1]) != 0:dp[j];
当把所有数全部看成一组的时候需要特殊处理
上代码
#include<stdio.h>
using namespace std;
const int N=1e5+10;
int sum[N]={0},dp[N]={0},n,x,mod=1e9+7;
int main(){
while(~scanf("%d",&n)){
for(int i=1;i<=n;i++){
scanf("%d",&x);
sum[i]=sum[i-1]+x;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(sum[i]-sum[j]!=0){
dp[i]+=dp[j];
dp[i]%=mod;
}
}
if(sum[i]!=0) dp[i]=(dp[i]+1)%mod;//这一步是整个数组全部为一组是否符合条件
}
printf("%d\n",dp[n]);
}
}
很明显N的规模为1e5,用n*n的算法肯定TLE了
哈希Hash
优化的就看有没有代码重复执行了
我们先看这一段
for(int j=1;j<i;j++){
if(sum[i]-sum[j]!=0){
dp[i]+=dp[j];
dp[i]%=mod;
}
}
每一次执行有需要把前面所有符合条件的加起来,太浪费时间了,我们可以把前面所有的dp[i]加一起为count,再把区间和为0的剔除掉就可行了,但是区间和为0的dp[j]的和怎么算呢?
这时候就用到Hash_map了,前缀和有一个好处就是所有相同的前缀和之间的区间的值的和为0,如果我们把所有前缀和为sum的dp[j]全部存入hash[sum]里,这样在剔除区间和为0的 dp[j] 的时候 就只需要一步减法操作就能完成了!上代码!
#include<bits/stdc++.h>
#include<hash_map>
using namespace std;
using namespace __gnu_cxx;
hash_map<int,int> h;
const int N=1e5+10;
int dp[N],n,x,mod=1e9+7;
int main(){
while(~scanf("%d",&n)){
h.clear();h[0]=1;/*这一步想当于前面处理全部数组划为1组的时候的情况*/
int sum=0,count=1;
for(int i=1;i<=n;i++){
scanf("%d",&x);
sum+=x;
dp[i]=(count-h[sum]+mod)%mod; /*剔除前缀和为sum的dp[j]*/
h[sum]+=dp[i];h[sum]%=mod; /*把当前的哈希表更新*/
count+=dp[i];count%=mod;
}
printf("%d\n",dp[n]);
}
}
由此可见dp[i]和前面的dp[j]并没有进行交互,直接把dp数组撤掉,使用一个变量last代替dp[ ]
#include<bits/stdc++.h>
#include<hash_map>
using namespace std;
using namespace __gnu_cxx;
hash_map<int,int> h;
const int N=1e5+10;
int n,x,mod=1e9+7;
int main(){
while(~scanf("%d",&n)){
h.clear();h[0]=1;
int sum=0,count=1,last=0;
for(int i=1;i<=n;i++){
scanf("%d",&x);
sum+=x;
last=(count-h[sum]+mod)%mod;
h[sum]+=last;h[sum]%=mod;
count+=last;count%=mod;
}
printf("%d\n",last);
}
}
总结
- 只要前面dp想出来了再想hash优化会更快,走来直接用hash就太难了.
- hash是个好东西要多用.
- 开始怕mod太大全部的数都用long long ,结果发现long long的if(sum[i]-sum[j]!=0)永远为真,不知道为什么.