1. 程式人生 > >單調佇列+dp 琪露諾+NOIP 2017 跳房子

單調佇列+dp 琪露諾+NOIP 2017 跳房子

一、琪露諾: 題意:一開始在00號格子上,每個格子有一個權值,在格子ii時,下一次可以移動到區間[i+l,i+r][i+l,i+r]中的任意一格,只要下一步的位置編號大於nn就算到達對岸,求最大權值。(n<=2e5)(n<=2e5)

首先如果不看資料範圍,這是一個普通的dp,設f[i]f[i]表示到達ii這個點的最大權值,得轉移方程:f[i]=max{f[ij]}+val[i],ljrif[i]=max\lbrace f[i-j] \rbrace +val[i],l≤j≤r≤i

+val[i],ljri

複雜度O(n2)O(n^2)

考慮如何優化,我們發現這個東西類似於滑動視窗,每次的區間是固定的。我們維護一個單調遞減的佇列,每到一個格子ii先將佇列中編號小於iri-r的元素出列(從隊首開始做),因為這些元素不能用於更新後面的答案了,然後將隊尾小於等於f[il]f[i-l]的元素出列,最後將f[il]f[i-l]壓入隊尾,則f[i]=a[i]+q[head]f[i]=a[i]+q[head]。最後答案就是max{f[i]},i[nr+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; }

二、跳房子: 題意:nn個格子,每個格子距離起點有距離,每個格子有得分。每次可以向右跳dd個距離,但可以花費gg使得每次可以向右跳[dg,d+g][d-g,d+g](如果dg&lt;1d-g&lt;1則為[1,d+g][1,d+g])個距離。只要跳到這個格子即會獲得這個格子的得分,問至少得kk分的情況下的最少花費是多少。

首先gg是可以二分的,對於每一個二分到的gg,我們需要用一個dp來檢驗最終得分是否大於kk,我們設dp[i]dp[i]表示跳到第ii個格子所能獲得的最大分數,轉移方程為:dp[i]=max{dp[ij]}+s[i],j[dg,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;
}