很明显的一个转移方程是: dp[i] = max(dp[j] + max(a[j+1], a[j+2], ..., a[i])); 其中满足 sum[i] - sum[j] <= M 的 j 都算,这是一个 O(N * N) 的解法,肯定超时。,那么就要用优化的方法 ,对着题解看了一个小时终于看懂了。
f[j]表示前j个数分为若干份,最优解。
g[i]表示使f[i]取得最优解的j,即最后一份为从g[i]分到i。
把第i+1个数加入进来。
如果超过了m,则把前面的数减去,直至小于m。
当总数小于m时:
如果a[i+1]小于mx(g[i]到i中的最大值)则可以忽略,f[i]=f[i-1],g[i]=g[i-1];
如果a[i+1]大于mx,则要确定最优值。(优化)因为f[i+1]的最优值不会再(g[i-1],i)内,所以可以直接从p到i+1计算。p为第p个数使p到i+1的总和小于m。但如果g[i-1]小于p,就要从i计算到p。
(g[i-1]>=p?g[i-1]:i);意思是如果之前的最大值不在当前字块内,那么就从i->p计算最大值之和的最小值;如果最大值还在的话,那么就只需要在计算g【i-1】== p处的最大值之和的最小值。其实就是更新一下f【i】和g【i】;如果f[i]>f[j-1]+mx的话,那么最优解肯定不是以g[i]为下标的那个解。那么更新f【i】=j;如果是f[i]<f[j-1]+mx,那么就不更新g[i]。
#include<iostream> #include <cstdio> using namespace std; #define maxn 101010 typedef long long ll; const ll inf=1000000000; ll f[maxn]; int g[maxn]; ll a[maxn]; int n; ll m; int main() { int i,j; while(scanf("%d%I64d",&n,&m)!=-1) { for(i=1; i<=n; i++) { scanf("%I64d",&a[i]); if(a[i]>m) { printf("-1\n"); return 0; } } f[1]=a[1]; g[1]=1; int p=1; f[0]=0;//因为当第一次更新的时候那个需要计算f[0]+mx,所以我们应该给f【0】赋值 ll mx=a[1],sum=a[1]; for(i=2; i<=n; i++) { int k=0; f[i]=inf; sum+=a[i]; while(sum>m)sum-=a[p++]; if(mx>=a[i]&&g[i-1]>=p) { f[i]=f[i-1]; g[i]=g[i-1]; } else { mx=a[i]; k=(g[i-1]>=p?g[i-1]:i); for(j=k; j>=p; j--) { mx=max(mx,a[j]); g[i]=f[i]>mx+f[j-1]?j:g[i]; f[i]=min(f[i],f[j-1]+mx); } mx=f[i]-f[g[i]-1]; } } printf("%I64d\n",f[n]); } }
原文地址:http://blog.csdn.net/u013076044/article/details/45697415