單調佇列+dp 琪露諾+NOIP 2017 跳房子
阿新 • • 發佈:2018-12-16
一、琪露諾: 題意:一開始在號格子上,每個格子有一個權值,在格子時,下一次可以移動到區間中的任意一格,只要下一步的位置編號大於就算到達對岸,求最大權值。
首先如果不看資料範圍,這是一個普通的dp,設表示到達這個點的最大權值,得轉移方程:
複雜度
考慮如何優化,我們發現這個東西類似於滑動視窗,每次的區間是固定的。我們維護一個單調遞減的佇列,每到一個格子先將佇列中編號小於的元素出列(從隊首開始做),因為這些元素不能用於更新後面的答案了,然後將隊尾小於等於的元素出列,最後將壓入隊尾,則。最後答案就是。
#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;
}
二、跳房子: 題意:個格子,每個格子距離起點有距離,每個格子有得分。每次可以向右跳個距離,但可以花費使得每次可以向右跳(如果則為)個距離。只要跳到這個格子即會獲得這個格子的得分,問至少得分的情況下的最少花費是多少。
首先是可以二分的,對於每一個二分到的,我們需要用一個dp來檢驗最終得分是否大於,我們設表示跳到第個格子所能獲得的最大分數,轉移方程為: 看到這個式子,發現是典型的單調佇列優化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;
}