【*】第三题:保龄球(提高组第二试2011年10月22日)(2011年NOIP冲刺模拟试题)

 

 保龄球

【问题描述】

你一个人保龄球馆去打保龄球。总共有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;
}


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值