单调队列+dp 琪露诺+NOIP 2017 跳房子

17 篇文章 0 订阅
2 篇文章 0 订阅

一、琪露诺:
题意:一开始在 0 0 0号格子上,每个格子有一个权值,在格子 i i i时,下一次可以移动到区间 [ i + l , i + r ] [i+l,i+r] [i+l,i+r]中的任意一格,只要下一步的位置编号大于 n n n就算到达对岸,求最大权值。 ( n &lt; = 2 e 5 ) (n&lt;=2e5) (n<=2e5)

首先如果不看数据范围,这是一个普通的dp,设 f [ i ] f[i] f[i]表示到达 i i i这个点的最大权值,得转移方程: f [ i ] = m a x { f [ i − j ] } + v a l [ i ] , l ≤ j ≤ r ≤ i f[i]=max\lbrace f[i-j] \rbrace +val[i],l≤j≤r≤i f[i]=max{f[ij]}+val[i],ljri

复杂度 O ( n 2 ) O(n^2) O(n2)

考虑如何优化,我们发现这个东西类似于滑动窗口,每次的区间是固定的。我们维护一个单调递减的队列,每到一个格子 i i i先将队列中编号小于 i − r i-r ir的元素出列(从队首开始做),因为这些元素不能用于更新后面的答案了,然后将队尾小于等于 f [ i − l ] f[i-l] f[il]的元素出列,最后将 f [ i − l ] f[i-l] f[il]压入队尾,则 f [ i ] = a [ i ] + q [ h e a d ] f[i]=a[i]+q[head] f[i]=a[i]+q[head]。最后答案就是 m a x { f [ i ] } , i ∈ [ n − r + 1 , n ] max \lbrace f[i]\rbrace,i∈[n-r+1,n] max{f[i]},i[nr+1,n]

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,l,r,val[1001000],ans;
int f[1001000];
struct node
{
	int val,pos;
}q[1001000];
int main()
{
	//freopen("testdata.in","r",stdin);
	cin>>n>>l>>r;
	for(int i=0;i<=n;++i)
		scanf("%d",&val[i]);
	int head=1,tail=0;
	for(int i=l;i<=n;++i)
	{
		while(head<=tail&&f[i-l]>=q[tail].val)
			tail--;
		tail++;
		q[tail].val=f[i-l];
		q[tail].pos=i-l;
		while(i-q[head].pos>r-l)
			head++;
		f[i]=q[head].val+val[i];	
	}
    for(int i=n-r+1;i<=n;++i)
		ans=max(ans,f[i]);
    printf("%d",ans);	
	return 0;
}

二、跳房子:
题意: n n n个格子,每个格子距离起点有距离,每个格子有得分。每次可以向右跳 d d d个距离,但可以花费 g g g使得每次可以向右跳 [ d − g , d + g ] [d-g,d+g] [dg,d+g](如果 d − g &lt; 1 d-g&lt;1 dg<1则为 [ 1 , d + g ] [1,d+g] [1,d+g])个距离。只要跳到这个格子即会获得这个格子的得分,问至少得 k k k分的情况下的最少花费是多少。

首先 g g g是可以二分的,对于每一个二分到的 g g g,我们需要用一个dp来检验最终得分是否大于 k k k,我们设 d p [ i ] dp[i] dp[i]表示跳到第 i i i个格子所能获得的最大分数,转移方程为: d p [ i ] = m a x { d p [ i − j ] } + s [ i ] , j ∈ [ d − g , d + g ] dp[i]=max\lbrace dp[i-j]\rbrace+s[i],j∈[d-g,d+g] dp[i]=max{dp[ij]}+s[i],j[dg,d+g]
看到这个式子,发现是典型的单调队列优化dp,这里单调队列维护的是格子的编号。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll l,r=1e7,ans,d,k;
int n;
ll x[1001000],s[1001000],dp[1001000],q[1001000];
bool check(int mid)
{
    for(int i=1;i<=n;++i)
		dp[i]=-1e9;   
    memset(q,0,sizeof(q));
    dp[0]=0;
	ll maxx,minn;
    if(mid>=d) 
		minn=1;
    else 
		minn=d-mid;
    maxx=d+mid;	
	int now=0;//now为未处理完的格子 
    int head=1,tail=0;
    for(int i=1;i<=n;i++)
	{
        while(x[i]-x[now]>=minn&&i>now)
        //把距离当前格minn内的未添加到单调队列的格子添加到单调队列 
		{
            if(dp[now]!=-1e9)
			{
                while(head<=tail&&dp[q[tail]]<=dp[now])
					tail--;
                tail++;
                q[tail]=now;
            }
            now++;
        }
        while(head<=tail&&x[i]-x[q[head]]>maxx) 
			head++;
        if(head<=tail)
			dp[i]=dp[q[head]]+s[i];
		if(dp[i]>=k) 
			return true;
    }
    return false;
}
ll sum=0;
int main()
{
	cin>>n>>d>>k;
	for(int i=1;i<=n;++i)
	{
		scanf("%lld%lld",&x[i],&s[i]);
		if(s[i]>0)
			sum+=s[i];
	}	
	if(sum<k)
	{
		cout<<"-1";
		return 0;
	}
	while(l<=r)
	{
		ll mid=(l+r)/2;
		if(check(mid))
		{
			r=mid-1;
			ans=mid;
		}
		else
			l=mid+1;
	}
	cout<<ans;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值