文章目录

  • 题目表述
  • 动态规划+前缀和
  • 哈希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);
	} 
}

总结

  1. 只要前面dp想出来了再想hash优化会更快,走来直接用hash就太难了.
  2. hash是个好东西要多用.
  3. 开始怕mod太大全部的数都用long long ,结果发现long long的if(sum[i]-sum[j]!=0)永远为真,不知道为什么.