[洛谷]P2627 修剪草坪 (#线性dp+单调队列)

84 篇文章 1 订阅
36 篇文章 0 订阅

题目描述

在一年前赢得了小镇的最佳草坪比赛后,Farm John变得很懒,再也没有修剪过草坪。现在,新一轮的最佳草坪比赛又开始了,Farm John希望能够再次夺冠。

然而,Farm John的草坪非常脏乱,因此,Farm John只能够让他的奶牛来完成这项工作。Farm John有N(1 <= N <= 100,000)只排成一排的奶牛,编号为1...N。每只奶牛的效率是不同的,奶牛i的效率为E_i(0 <= E_i <= 1,000,000,000)。

靠近的奶牛们很熟悉,因此,如果Farm John安排超过K只连续的奶牛,那么,这些奶牛就会罢工去开派对:)。因此,现在Farm John需要你的帮助,计算FJ可以得到的最大效率,并且该方案中没有连续的超过K只奶牛。

输入格式

第一行:空格隔开的两个整数 N 和 K

第二到 N+1 行:第 i+1 行有一个整数 E_i

输出格式

第一行:一个值,表示 Farm John 可以得到的最大的效率值。

输入输出样例

输入 #1

5 2
1
2
3
4
5

输出 #1

12

思路

动态规划+单调队列好题。

先考虑使用线性dp。令dp[i]表示前i头奶牛能得到的最大效率。在第i头奶牛,一定在区间[i-k,i]内有奶牛j得休息。在区间[i-k,j]内枚举休息的奶牛,则

dp[i]=max(dp[i],dp[i-1]+sum[i]-sum[j])

其中sum[i]表示前缀和,即奶牛区间[1,i]的效率和,sum[i]-sum[j]为奶牛区间[j,i]的效率和。答案为dp[n]。这个方程还是比较容易的吧。

于是我们写出代码,发现只有60分。2个WA,2个TLE。

#include <stdio.h>
#include <iostream>
using namespace std;
int n,k,a[100001],s,dp[100001],sum[100001];
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register int i,j;
	cin>>n>>k;
	for(i=1;i<=n;i++)
	{
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];//前缀和 
	}
	
	for(i=1;i<=n;i++)
	{
		for(j=i-k;j<=i;j++)
		{
			dp[i]=max(dp[i],dp[j-1]+sum[i]-sum[j]);
		}
	}
	cout<<dp[n]<<endl;
	return 0;
}

为什么会T呢?因为n达到了10^5。显然2层循环会TLE。

我们再仔细思考一下,发现如果想要dp[i]尽可能大,不就是想让dp[j-1]+sum[i]-sum[j]尽可能大吗?

那我们把方程变形,得:

dp[i]=max(dp[i],dp[j-1]-sum[j])+sum[i]

此时i-k<=j<=i。

即把sum[i]放到外面,这样保证了想让dp[i]尽可能大,就只和j有关系了,即max内的值只和j有关。现在只需要用单调队列维护dp[j-1]-sum[j]就好了。

按照这个思路,还是60。。WA4个点,发现这题要开long long。。不开long long见祖宗啊。

#include <stdio.h>
#include <iostream>
#include <deque>
#define ll long long int
using namespace std;
ll n,k,a[100001],s,dp[100001],sum[100001];
struct node
{
	ll v,position;//v为权,position是下标 
};
deque<node> dq;
inline void update(ll i)
{
	ll x(dp[i-1]-sum[i]);//维护dp[j-1]-sum[j] 
	while(!dq.empty() && dq.back().v<=x) dq.pop_back();//如果队尾小于dp[j-1]-sum[j],全扔掉 
	dq.push_back({x,i});//新元素放进去 
	
}
inline ll query(ll i)
{
	while(!dq.empty() && dq.front().position<i-k) dq.pop_front();//检查是否在区间[i-k,i]内 
	return dq.front().v;
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register ll i,j;
	cin>>n>>k;
	for(i=1;i<=n;i++)
	{
		cin>>a[i];
		sum[i]=sum[i-1]+a[i];//前缀和 
	}
	dq.push_back({0,0});//先压一个元素 
	for(i=1;i<=n;i++)
	{
		update(i);
		dp[i]=query(i)+sum[i];//dp[i]=max(dp[i],dp[j-1]-sum[j])+sum[i]
	}
	cout<<dp[n]<<endl;
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值