[USACO11FEB]Generic Cow Protests
题目传送门
一道挺好的数据结构优化DP的题目,树状数组搞一搞就好了。
sol
讲下思路,首先看了眼数据范围 \(1≤N≤10^5\) ,暴力很好写吧,\(O(n^2)\)的线性dp
for(int i=1;i<=n;i++)scanf("%d",&f[i]),s[i]=s[i-1]+f[i];//统计一下前缀和
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
if(s[i]-s[j]>=0)dp[i]+=dp[j],dp[i]%=mod;//线性dp
}
}
printf("%d\n",dp[n]);//目标dp[n]
然后我们发现
前缀和数组\(s_i\),dp数组\(f_i\),最终答案为\(f_n\)
那么怎么计算答案呢,看下样例(其中\(0≤i≤n\))
\(i\) | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
\(a_i\) | 0 | 2 | 3 | -3 | 1 |
\(s_i\) | 0 | 2 | 5 | 2 | 3 |
因为数的范围为\(-10^5 ≤ Ai ≤ 10^5\),所以需要离散化一下防止RE,设离散化数组为\(r_i\),也就是每个\(s_i\)的排名 |
\(i\) | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
\(r_i\) | 1 | 2 | 4 | 2 | 3 |
因为要保证\(s_i≥s_j\)(\(1≤i≤n,0≤j<i\)),\(f_i\)加上之前比他小的有多少方案(其实就是一个加法原理)
我们设值域为数的种类,建立一个树状数组,其中第\(r_i\)位表示这个数之前有多少个数小于等于他,画个图理解一下
当\(i\)=0时,直接加上
当\(i\)=1时,因为\(r_1\)=2,所以在第二个位置加入,又因为在第二个位置前有1个数加入,所以第二个位置的值为1
当\(i\)=2时,因为\(r_2\)=4,所以在第四个位置加入,又因为在第四个位置前有2个数加入,所以第二个位置的值为2
注意当\(i\)=3时,\(r_3\)=2,从前出现过,但还是一样,在第二个位置加入,先统计,再加入
因为第二个位置之前有2个数加入,所以在第二个位置加上2,故
当\(i\)=4时,以此类推,所以最后的答案为4
cpp
#include<bits/stdc++.h>
#define mod 1000000009
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
int n;
int tree[maxn];
inline int lowbit(int x){return x&(-x);}//树状数组基本操作
void add(int x,int d){for(int i=x;i<=1e5+10;i+=lowbit(i))tree[i]+=d,tree[i]%=mod;}//添加操作 记得取模
inline int sum(int x){ll sum=0;for(;x;x-=lowbit(x))sum+=tree[x],sum%=mod;return sum;}//求和操作 记得取模
int s[maxn],f[maxn];//s为前缀和
ll ans;
int b[maxn];//离散化数组
int main(){
scanf("%d",&n);
for(int i=1,x;i<=n;i++)scanf("%d",&x),s[i]=s[i-1]+x,b[i]=s[i];//用离散化的数组记录起来
sort(b,b+n+1);
for(int i=0;i<=n;i++)s[i]=lower_bound(b,b+n+1,s[i])-b+1;//离散化
add(s[0],1);//s[0]方案数为1
for(int i=1;i<=n;i++){
ans=sum(s[i]);//查询在s[i]前有多少小于等于它的数
add(s[i],ans);//加入到树状数组中
}
printf("%lld\n",ans%mod);//记得取模
return 0;
}
完结撒花,欢迎大佬爆锤我。