出处: ?
主要算法:dp+单调队列优化
难度:4.3
思路分析:
Dp还是容易看出来的。
我的第一感觉是一维,f[i]表示前i头奶牛的最大效率。其实这也是可以解的,具体方法将会在后文介绍。
考虑二维的解法,f[i][0]表示奶牛i不参与时的最大效率,f[i][1]表示奶牛i参与。我们知道,在前k头奶牛中必定有一头奶牛不参与——对于f[i][0]转移很简单,由于奶牛i不参与,一定是选择继承,所以必定有f[i][0] = Max(f[i-1][0], f[i-1][1]). 而f[i][1]可以利用f[i-j][0](0<j<K)来转移:f[i][1] = Max(f[i-j][0] + sum[i] - sum[i-j])
这个方程的意思就是i-j这头奶牛不选,并继承最优子结构f[i-j][0],然后i-j之后的奶牛全部选择,于是用一个前缀和来维护即可。整理方程发现,sum[i]是确定的,于是可以将它提出max之外,得到f[i][1] = Max(f[i-j][0] - sum[i-j]) + sum[i],我们发现这个方程就只与i-j有关了,并且是个定长区间的最大值——很容易让我们联想到滑动窗口问题,于是通过单调队列来解决就好了。
下面的代码贴的是以上之中方法……
刚才我们提到了可以用一维来解决,即f[i]表示前i头奶牛的最大效率。其实是与二维一模一样的,二维实在是多此一举。由于i-j根本不选,我们可以直接继承f[i-j-1],在加上前缀和,就有了方程f[i][1] = Max(f[i-j-1] - sum[i-j]) + sum[i].
代码注意点:
long long
/*By QiXingzhi*/ #include <cstdio> #define N (100010) #define INF (0x3f3f3f3f) #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) #define r read() typedef long long ll; #define int ll using namespace std; inline int read(){ int x = 0; int w = 1; register int c = getchar(); while(c ^ '-' && (c < '0' || c > '9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c >= '0' && c <= '9') x = (x << 3) +(x << 1) + c - '0', c = getchar(); return x * w; } int n,k,h=1,t; int a[N],f[N][2],q[N],s[N]; inline void Push(int w){ while(h<=t && f[w][0]-s[w] > f[q[t]][0]-s[q[t]]) --t; q[++t] = w; } #undef int int main(){ // freopen(".in","r",stdin); #define int ll n=r,k=r; for(int i = 1; i <= n; ++i){ a[i]=r; s[i] = s[i-1]+a[i]; } f[1][0] = 0; f[1][1] = a[1]; Push(0); Push(1); for(int i = 2; i <= n; ++i){ f[i][0] = Max(f[i-1][0], f[i-1][1]); while(h<=t && q[h] < i-k) ++h; f[i][1] = f[q[h]][0] + s[i] - s[q[h]]; Push(i); } printf("%lld",Max(f[n][0],f[n][1])); return 0; }