bzoj2376/poj3350 保龄球

单调队列优化DP和处理边界情况好题,模拟赛后挂了好几发才过(雾)

这是一篇不完全算题解的解题报告(雾)

bzoj2376/poj3350

Description

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

Constraints

\(1≤n≤10000\)\(1≤w≤100\)\(1≤k≤500\)\(x_{i}\) 会有负数。

Solution

不难想到用 DP 来做,设 \(f[i][j]\) 表示考虑前 \(i\) 个球棒至多用 \(j\) 个球(不一定全用完) 的最大价值,但由于打掉一些球棒后会出现后效性,需要修改状态设计。

如果我们强制要求第 \(i\) 个球棒必须在这一轮打掉,则前面的状态得以确定,并且不会影响这一次的状态。

所以状态设计改为:考虑 \(f[i][j]\) 表示考虑前 \(i\) 个球棒,且必要在这轮打倒第 \(i\) 个球棒,至多用 \(j\) 个球的最大价值。

枚举 \(k\),上一次打到了哪里,分与这一次的打没有或又重叠两部分(重叠的意思就是这一次打不到完整的 \(w\) 个球棒),分开转移。

  • 如果 \(k \lt i - w\),无重叠,这次可以获得 \(sum(i - w, i)\) 的价值。

  • 如果 \(k \ge i - w\),会有重叠,这次可以获得 \(sum(k, i)\) 的价值。

初始值一开始全为 \(-INF\), 对于每个位置 \(i\),如果只打一个球就是长度为 \(w\) 的一段,\(f[i][1] = sum[i] - sum[max(i - w, 0)]\)

时间复杂度为:\(O(N^2K)\)

主要代码如下:(题干中的 \(k\)\(kk\)注意,这个代码只有 \(0\) 分,因为还未考虑全

但是,样例通过了,交上去:



我们开始思考,是哪里的边界没有处理好?

面向数据,看看第一组数据:

正解:\(17858\),输出:\(12547\)

调试看出,程序只是计算出了前面的最大值,而最后单独从右边打掉 \(1\) 个的 \(5311\) 没有计算。

为了解决右边的极端情况,我们可以考虑右边再加 \(k\) 个虚拟球棒,价值均为 \(0\),转移时一起转移,最后取 \(\max\) 时也延长到 \(n + k\)即可。

	scanf("%lld%lld%lld", &n, &kk, &w);
	memset(f, ~0x3f, sizeof f); 
	for(int i = 1; i <= n; i++)
	{
		scanf("%lld", &a[i]);
		sum[i] = sum[i - 1] + a[i];
		f[i][1] = sum[i] - sum[max(i - w, (long long)0)];	
	}
	for(int i = n + 1; i <= n + kk; i++)
	{
		sum[i] = sum[i - 1];
		f[i][1] = sum[i] - sum[max(i - w, (long long)0)];//延长部分也要预处理	
	}
	for(int j = 1; j <= kk; j++)
	{
		for(int i = 1; i <= n + kk; i++)//转移也要到 n + kk
		{
			f[i][j] = max(f[i][j], f[i][j - 1]);
			for(int k = 0; k <= i - 1; k++)
			{
				if(k >= i - w)
					f[i][j] = max(f[i][j], f[k][j - 1] + sum[i] - sum[k]);
				else
					f[i][j] = max(f[i][j], f[k][j - 1] + sum[i] - sum[i - w]);
			}
		}
	}
	int ans = 0;
	rep(i, 1, n + kk)
		rep(j, 0, kk)
			ans = max(ans, f[i][j]);
	printf("%lld", ans);

由于复杂度太大,还是在第 3 个点上 TLE 了。



考虑是否能优化。状态无法优化,只能考虑转移优化。

把方程写出来:

\[\large f[i][j]=\left\{ \begin{matrix} \max(f[i][j], f[k][j - 1] + sum[i] - sum[i - w]) (k \lt i - w)\\ \max(f[i][j], f[k][j - 1] + sum[i] - sum[k]) (k \ge i - w) \end{matrix} \right.\]

对于第一种转移,发现 \(sum[i] - sum[i - w]\) 为定值, \(k\) 只会影响 \(f[k][j -1]\),我们可以再设一个数组 \(maxn[i][j]\),表示 \(f[i][j-1](1 \le i \le i - w)\) 的最大值,转移时也带上 \(maxn\) 数组转移即可,这样这种转移复杂度为 \(O(1)\)

对于第二种转移,\(k\) 会影响里面大多元素,但是,在 \(i\) 向后枚举递增时,我们需要的 \(k\) 的区间也在递增,不难啊想到用单调队列优化转移。

\(sum[i]\) 为定值,那我们把 \(f[k][j - 1] - sum[k]\) 放入单调队列里,我们需要的区间即为 \(k > i - w\),在转移时更新单调队列的头尾指针即可。

// by youyou2007 in 2022.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
#define int long long
#define REP(i, x, y) for(int i = x; i < y; i++)
#define rep(i, x, y) for(int i = x; i <= y; i++)
#define PER(i, x, y) for(int i = x; i > y; i--)
#define per(i, x, y) for(int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
/*
考虑 f[i][j] 表示前 i 个位置且 i 位置必打,用 j 个球打的最大分数
f[i][j] = max(f[k][j - 1]) + sum(j - k + 1 ~ k)
注意要到 n + k,因为考虑左右区间极端情况 
*/
const int N = 10005 + 505;
const int K = 505;
int n, kk, w;
int f[N][K], maxn[N][K];
int sum[N], a[N];
int que[N * 2], head, tail;
signed main()
{
	scanf("%lld%lld%lld", &n, &kk, &w);
	memset(f, ~0x3f, sizeof f); 
	memset(maxn, ~0x3f, sizeof maxn);
	for(int i = 1; i <= n; i++)
	{
		scanf("%lld", &a[i]);
		sum[i] = sum[i - 1] + a[i];
		f[i][1] = sum[i] - sum[max(i - w, (long long)0)];
		maxn[i][1] = max(maxn[i - 1][1], f[i][1]);	
	}
	for(int i = n + 1; i <= n + kk; i++)
	{
		sum[i] = sum[i - 1];
		f[i][1] = sum[i] - sum[max(i - w, (long long)0)];
		maxn[i][1] = max(maxn[i - 1][1], f[i][1]);		
	}
	for(int j = 2; j <= kk; j++)
	{
		head = 0, tail = 1;
		for(int i = 1; i <= n + kk; i++)
		{
/*			f[i][j] = max(f[i][j], f[i][j - 1]);
			for(int k = 0; k <= i - 1; k++)
			{
				if(k >= i - w)
					f[i][j] = max(f[i][j], f[k][j - 1] + sum[i] - sum[k]);
				else
					f[i][j] = max(f[i][j], f[k][j - 1] + sum[i] - sum[i - w]);
			}*/
			if(i <= w)
			{
				f[i][j] = sum[i]; 
			}
			else
			{
				f[i][j] = max(f[i][j], maxn[i - w][j - 1] + sum[i] - sum[i - w]);
				while(head <= tail && que[head] < i - w)
				{ 
					head++;
				}
				while(head <= tail && f[i][j - 1] - sum[i] > f[que[tail]][j - 1] - sum[que[tail]])
				{
					tail--;
				}
				que[++tail] = i;
				f[i][j] = max(f[i][j], f[que[head]][j - 1] + sum[i] - sum[que[head]]);
			}
			maxn[i][j] = max(maxn[i - 1][j], f[i][j]);
		}
	}
	int ans = 0;
	rep(i, 1, n + kk)
		rep(j, 0, kk)
			ans = max(ans, f[i][j]);
	printf("%lld", ans);
	return 0;
}
posted @ 2022-07-26 23:48  panjx  阅读(33)  评论(0编辑  收藏  举报