【题目大意】:给出n个数,现在可以按顺序随意的把n个数划分成任意部分,问使得每一部分的和均不小于0的划分方式有多少种。
【解题思路】:dp的转移方程很显然,dp[i]表示前i个数,对其进行任意划分之后满足题设的种类有多少种。则dp[j]=sigema(dp[i]) (0<=i<j && sum[i]<=sum[j]) 这里sum指的是前n个数的和。其中dp[0]=1;
然后...这样写显然会超时....我们要进行优化。-_-!!!!!~...
我们可以发现,其实每一次dp值的更改其实只是跟前面sum小于等于其当前所在的sum值的时候发生,而且对于相同的sum值所对应的,可以在前一个的基础上进行修改
5
2 3 -5 2 1为例...
sum 0 2 5 0 2 3
dp 1 1 2 1 3 6
我们无需理会两个相同的sum之间相隔多少个数...我们只需要知道比其小的sum值有多少就可以了...也就是可以按顺序更新dp值...像什么...树状数组...
c[]来存储树状数组,我们首先先对sum进行离散,并去重,因为我们关心的是比其小的数,相等的数可以根据比其小的数进行更新。
也就是,我们每一步都可以得到一个dp值,它是小于等于当前sum的方法的和。然后我们再更新当前点的方法数就可以了....说得各种混乱...看代码吧...
【代码】:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <cmath>
#include <string>
#include <cctype>
#include <map>
#include <iomanip>
using namespace std;
#define eps 1e-8
#define pi acos(-1.0)
#define inf 1<<30
#define linf 1LL<<60
#define pb push_back
#define lc(x) (x << 1)
#define rc(x) (x << 1 | 1)
#define lowbit(x) (x & (-x))
#define ll long long
#define mod 1000000009
int n,a[100005],c[100005];
int sum[100005],summ[100005],cnt;
void update(int x,int val){
for (int i=x; i<=cnt; i+=lowbit(i))
c[i]=(c[i]+val)%mod;
}
int getsum(int x){
int sum=0;
for (int i=x; i>0; i-=lowbit(i))
sum=(sum+c[i])%mod;
return sum;
}
int find(int x){
return lower_bound(summ,summ+cnt,x)-summ;
}
int main (){
while (~scanf("%d",&n)){
sum[0]=0;
for (int i=1; i<=n; i++){
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
sort(sum,sum+n+1);
cnt=0;
int tmp=sum[0];
summ[0]=tmp;
for (int i=1; i<=n; i++){
if (sum[i]!=tmp) {
cnt++;
summ[cnt]=sum[i];
tmp=summ[cnt];
}
}
cnt++;
update(find(0)+1,1);
int ans;
tmp=0;
for (int i=1; i<=n; i++){
tmp+=a[i];
ans=getsum(find(tmp)+1);
update(find(tmp)+1,ans);
}
printf("%d\n",ans);
}
return 0;
}