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

版权声明:未经本蒟蒻同意,请勿转载本蒻博客 https://blog.csdn.net/wddwjlss/article/details/83215646

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

首先如果不看数据范围,这是一个普通的dp,设 f [ i ] f[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

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

考虑如何优化,我们发现这个东西类似于滑动窗口,每次的区间是固定的。我们维护一个单调递减的队列,每到一个格子 i i 先将队列中编号小于 i r i-r 的元素出列(从队首开始做),因为这些元素不能用于更新后面的答案了,然后将队尾小于等于 f [ i l ] f[i-l] 的元素出列,最后将 f [ i l ] f[i-l] 压入队尾,则 f [ i ] = a [ i ] + q [ h e a d ] 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]

#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 个格子,每个格子距离起点有距离,每个格子有得分。每次可以向右跳 d d 个距离,但可以花费 g g 使得每次可以向右跳 [ d g , d + g ] [d-g,d+g] (如果 d g &lt; 1 d-g&lt;1 则为 [ 1 , d + g ] [1,d+g] )个距离。只要跳到这个格子即会获得这个格子的得分,问至少得 k k 分的情况下的最少花费是多少。

首先 g g 是可以二分的,对于每一个二分到的 g g ,我们需要用一个dp来检验最终得分是否大于 k k ,我们设 d p [ i ] dp[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,这里单调队列维护的是格子的编号。

#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;
}

猜你喜欢

转载自blog.csdn.net/wddwjlss/article/details/83215646