保龄球
【问题描述】
你一个人保龄球馆去打保龄球。总共有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 <[email protected]> ** 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; }