(P5858 )「SWTR-03」Golden Sword(动态规划+单调队列优化)

55 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

题目链接:「SWTR-03」Golden Sword - 洛谷

image.png

分析:读完题很容易发现这是一个dp题,我们设f[i][j]表示加完第i种原料后还剩j种原料的最大耐久度之和,我们来看看f[i][j]可以由哪些状态来转移得到,很显然它是由加完第i-1种原料的一些状态得到的,现在我们来想想应该加完第i-1种原料剩下多少种原料才能转移得到f[i][j]呢?其实这个也不是很难想,首先f[i][j-1]这个肯定可以,我们直接加上第i种原料即可,其次f[i-1][j]也可以,我们可以先去掉一种原料然后再加上第i种原料,依次类推,f[i-1][j-1+s]也可以,无非就是我们先去掉s种原料再加上第i种原料罢了,最后别忘了加上当前原料的贡献,注意的是我们不能使剩余的原料个数大于w,当然这个也比较好判断。下面来看一下代码:(这个代码是一个超时代码)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N=5e3+10;
ll f[N][N];//f[i][j]表示放完第i种原料后锅内还有j种原料的最大耐久度之和 
ll a[N];
int main()
{
	int n,w,s;
	cin>>n>>w>>s;
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	memset(f,-0x3f,sizeof f);
	f[0][0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=min(w,i);j++)
		for(int k=j-1;k<=min(w,j-1+s);k++)
			f[i][j]=max(f[i][j],f[i-1][k]+a[i]*j);
	}
	ll ans=-0x3f3f3f3f;
	for(int i=1;i<=w;i++)
		ans=max(ans,f[n][i]);
	printf("%lld",ans);
	return 0;
}

注意:上述代码会超时,因为三重for循环,所以复杂度太高,现在我们来看看能不能对这道题目进行一下优化,我们先来看一下最里面的一层for循环,就是枚举f[i-1][j-1~j-1+s]然后找一个最大值,但是这个找最大值的过程我们是o(n)的,而且这个过程我们需要遍历多次,大家有没有发现这个问题似曾相识,就是滑动窗口的那个问题,每次给定一个窗口的范围,然后找这个窗口的最大值或者最小值,不知道这道题的同学可以看下我之前的博客,下面附上博客地址:滑动窗口(单调队列)_AC__dream的博客-CSDN博客

而这道题的优化方式与滑动窗口那道题非常像,唯一的不同就是这道题的查询区间不是固定的,但本质解法上是一模一样的,我们把最后两层for循环优化成一层for循环就可以通过这道题目了,特别需要注意的一点就是我们每次查找最大值之前都需要把队列清空,这点非常重要,就是这个点卡了我一天(+-+),就是希望大家注意一下吧,下面附上代码,希望大家好好理解一下,这种优化方式我觉得还是非常巧妙的。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N=5e3+10;
ll q[N],tt,hh;
ll f[N][N];//f[i][j]表示放完第i种原料后锅内还有j种原料的最大耐久度之和 
ll a[N];
int main()
{
	int n,w,s;
	cin>>n>>w>>s;
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	memset(f,-0x3f,sizeof f);
	f[0][0]=0;
	for(int i=1;i<=n;i++)
	{
		hh=tt=0;//一定不要忘记初始化
		for(int j=1,k=0;j<=min(w,i);j++)//单调队列优化 
		{
			while(k<=min(w,j-1+s))//把更新f[i][j]的f[i-1][k]全部入队列
			{
				while(hh<tt&&f[i-1][q[tt]]<=f[i-1][k]) tt--;
				q[++tt]=k++;
			}
			while(hh<tt&&q[hh+1]<j-1) hh++;
			f[i][j]=f[i-1][q[hh+1]]+a[i]*j;
		}
	}
	ll ans=-0x3f3f3f3f3f3f3f3f;
	for(int i=1;i<=w;i++)
		ans=max(ans,f[n][i]);
	printf("%lld",ans);
	return 0;
}