您的位置:首页 > 其它

[noip2010模拟] 保龄球

2017-01-22 08:31 267 查看

题目描述

你一个人保龄球馆去打保龄球。总共有k个球可用。每个球的宽度为w。在你前方有n个球棒要打。这n球棒紧密的排成一行,且第i个球棒宽度为1,价值为xi。你的每个球恰能击中第a个~第a+w-1个的球棒(如果此球棒存在的话)。球棒被打到就倒了,且互不影响。你可以向任意方向击球,甚至球的一部分可以越过最左、最右边球棒所构成的边界。求最大价值。

输入格式

文件第一行是三个整数n,k,w。以下n行是n个整数,第i个代表x(i)。

输出格式

仅一行,为最大价值。

样例数据

样例输入

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个球不一定打完,但第j个一定打倒的最大价值。

做一次前缀和sum[]

设前一次打到t个保龄球

f[i,j]=f[i-1,t]+sum[j]-sum[j-w] (1<=t<=j-w)

f[i,j]=f[i-1,t]+sum[j]-sum[t] (j-w < t < j)

观察第一个方程,f[i-1,t]最大值在动规过程中即可统计出来。

观察第二个方程,sum[j]是定值,f[i-1,t]-sum[t]可以用单调队列维护最大值。

故O(nk)解决此题

源代码

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
inline const int Get_Int() {
int num=0,bj=1;
char x=getchar();
while(x<'0'||x>'9') {
if(x=='-')bj=-1;
x=getchar();
}
while(x>='0'&&x<='9') {
num=num*10+x-'0';
x=getchar();
}
return num*bj;
}
int n,k,w,sum[15005],f[505][15005],ans=0;
int main() {
n=Get_Int();
k=Get_Int();
w=Get_Int();
for(int i=1; i<=n; i++)sum[i]=sum[i-1]+Get_Int();
for(int i=n+1; i<=n+w; i++)sum[i]=sum[i-1];
for(int i=1; i<=n+w; i++)f[1][i]=sum[i]-sum[max(i-w,0)];
for(int i=2; i<=k; i++) { //打k次保龄球
int Max=-0x7fffffff/2;
deque<int>Q;
Q.push_back(0);
for(int j=1; j<=n+w; j++) {
while(!Q.empty()&&Q.front()<j-w)Q.pop_front(); //维护队首
if(j-w>=0)Max=max(Max,f[i-1][j-w]); //将方程1的最大值维护走
f[i][j]=max(f[i-1][Q.front()]-sum[Q.front()],Max-sum[j-w])+sum[j];
while(!Q.empty()&&f[i-1][Q.back()]-sum[Q.back()]<=f[i-1][j]-sum[j])Q.pop_back(); //维护队尾
Q.push_back(j);
ans=max(ans,f[i][j]);
}
}
printf("%d\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息