保龄球
【问题描述】
你一个人保龄球馆去打保龄球。总共有k个球可用。每个球的宽度为w。在你前方有n个球棒要打。这n球棒紧密的排成一行,且第i个球棒宽度为1,价值为xi。你的每个球恰能击中第a个~第a+w-1个的球棒(如果此球棒存在的话)。球棒被打到就倒了,且互不影响。你可以向任意方向击球,甚至球的一部分可以越过最左、最右边球棒所构成的边界。求最大价值。
【输入】
文件第一行是三个整数n,k,w。以下n行是n个整数,第i个代表xi。
【输出】
输出文件仅一行,为最大价值。
【样例输入输出】
bowling.in | bowling.out |
9 2 3 2 8 5 1 9 6 9 3 2 | 39 |
【数据范围】
对于40%的数据,满足1≤n,w,k≤100;
对于100%的数据,满足1≤n≤10000,1≤w≤100,1≤k≤500;
解题思路:
设f[i,j]表示用前i个球去打前j个棒,且第j个棒一定要打倒时所能获得的最大价值。则
f[i,j]=max{f[i-1,k(0<=k<=j-w)]+s[j]-s[j-w],f[i-1,k(j-1>=k>=j-w+1)]+s[j]-s[k]}。
(1)对于第一种情况:就是第i个球,打了最多个棒子,即把第j-w+1个棒子到第j个棒子全部打倒,此时第i个球得分为s[j]-s[j-w]。对于这种情况前i个球的得分就是f[i-1,k]+s[j]-s[j-w]。其中0<=k<=j-w。
(2)对于第二种情况就是第i个球,没有打最多,就第j-w+1个棒子到第i个棒子中有一部分是被第i-1个球打倒的。这种情况第i个球的得分就是s[i]-s[k],其中j-w+1<=k<=j-1。
临界状态f[i,0]=0,f[i,1]=s[1];
直接这样做肯定会超时。怎样优化呢?
让我们从两个式子特点入手,逐一优化方程。
(1)第一个式子中s[j]-s[j-w]为定值,与k无关,且左边界为常数。那么很容易想到令g[i,j]=max{g[i,k(j>=k>=0)]},这样决策就优化到了O(1)
(2)第二个式子中s[j]-s[k]不是定值,且k的左边界为变量。这样就不能用上面类似的方法来优化了。但我们注意到:当j递增时,k的取值区间也是递增的,且长度一定,这就让我们想起了经典的用队列维护最值的模型。于是我们把f[i-1,k]-s[k]存入队列里,然后把k<i-len的决策从队列中删除,最后得到的队首元素的值就是max{f[i-1,k]-s[k]},然后就可以得出此决策的结果了。平均复杂度也是O(1)的。
所以总时间复杂度:O(nm)。
【参考代码】(第二种情况没细看)
/******************************************************************************************************
** Copyright (C) 2011.07.01-2013.07.01
** Author: famousDT <13730828587@163.com>
** Edit date: 2011-10-25
******************************************************************************************************/
#include <stdio.h>
#include <stdlib.h>//abs,atof(string to float),atoi,atol,atoll
#include <math.h>//atan,acos,asin,atan2(a,b)(a/b atan),ceil,floor,cos,exp(x)(e^x),fabs,log(for E),log10
#include <vector>
#include <queue>
#include <map>
#include <time.h>
#include <set>
#include <list>
#include <stack>
#include <string>
#include <iostream>
#include <assert.h>
#include <string.h>//memcpy(to,from,count
#include <ctype.h>//character process:isalpha,isdigit,islower,tolower,isblank,iscntrl,isprll
#include <algorithm>
using namespace std;
typedef long long ll;
#define MY_PI acos(-1)
#define MY_MAX(a, b) ((a) > (b) ? (a) : (b))
#define MY_MIN(a, b) ((a) < (b) ? (a) : (b))
#define MY_MALLOC(n, type) ((type *)malloc((n) * sizeof(type)))
#define MY_ABS(a) (((a) >= 0) ? (a) : (-(a)))
#define MY_INT_MAX 0x7fffffff
int n, k, w;
int a[20005];
long long int s[20005];
long long int g[505][20005];
long long int f[505][20005];
int Queue[20005];
/*
9 2 3
2 8 5 1 9 6 9 3 2
*/
int main()
{
FILE *in, *out;
in = freopen("bowling.in", "rt", stdin);
out = freopen("bowling.out", "wt", stdout);
scanf("%d%d%d", &n, &k, &w);
int i, j;
for (i = 1; i <= n; ++i) scanf("%d", &a[i]);
n += w;
for (i = 1; i <= n; ++i) s[i] = s[i - 1] + a[i];
g[1][0] = -0xffffff;
f[1][0] = -0xffffff;
long long int ans = -0xffffff;
for (i = 1; i <= n; ++i) {//一个球打所有球棒
f[1][i] = s[i] - s[(max(i - w, 0))];
g[1][i] = max(g[1][i - 1], f[1][i]);//记录一个球时最大值
ans = max(ans, f[1][i]);//保存一个球时的结果
}
int st = 1;
for (i = 2; i <= k; ++i) {//从第二个球开始
st = 1 - st;//第一次为0,实现滚动数组
for (j = 0; j <= n; ++j) f[st][j] = g[st][j] = -0xffffff;
int close = 0;
int open = 0;
for (j = 1; j <= n; ++j) {
if (j >= w) {
f[st][j] = max(f[st][j], g[1 - st][j - w] + s[j] - s[j - w]);//第一种情况
while (f[1 - st][Queue[open - 1]] + s[j] - s[Queue[open - 1]] < f[1 - st][j] && close < open) --open;
Queue[open++] = j;
while (close < open && Queue[close] < j - w) close++;
f[st][j] = max(f[st][j], f[1 - st][Queue[close]] + s[j] - s[Queue[close]]);
} else f[st][j] = s[j];
g[st][j] = max(g[st][j - 1], f[st][j]);//保存当前最大值
ans = max(ans, f[st][j]);
}
}
printf("%lld\n", ans);
system("pause");
return 0;
}