洛谷luogu3957跳房子(单调队列优化)
QwQ被普及组的题折磨的死去活来。
硬是卡线段树,没卡过QwQ
oi生涯,第一道正经的单调队列dp题
进入正题
题目大意:
其中\(n \le 500000\)
看到这个题的第一感觉就是二分金币数
很显然,如果\(mid\)可以,那么\(mid+1\)一定可以
但是QwQ
不要想\(nlog^2n\)了(卡了一下午没卡过去)
首先考虑裸的\(dp\)
\(dp[i]=max(dp[j])+val[i]\)
其中\(mindis<=|dis[i]-dis[j]|<=maxdis\)
这种做法应该是\(O(n^2logn)\)的
这里就不放代码了
取max的过程可以用线段树来优化QwQ虽然和暴力一个分 但是我只写了这种暴力。。。放一波代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 1000010;
int x[maxn],val[maxn];
int n,d,k;
int dp[maxn],f[4*maxn];
int add[4*maxn];
void up(int root)
{
f[root]=max(f[2*root],f[2*root+1]);
}
void pushdown(int root,int l,int r)
{
if (add[root])
{
add[2*root+1]=add[root*2]=add[root];
f[2*root]=add[root];
f[2*root+1]=add[root];
add[root]=0;
}
}
void build(int root,int l,int r)
{
add[root]=0;
if (l==r)
{
f[root]=-1;
return;
}
int mid =(l+r) >> 1;
build(2*root,l,mid);
build(2*root+1,mid+1,r);
up(root);
}
void update(int root,int l,int r,int x,int y,int p)
{
if (l>r || x>y) return;
if (x<=l && r<=y)
{
add[root]=p;
f[root]=p;
return;
}
int mid = (l+r) >>1;
pushdown(root,l,r);
if (x<=mid) update(2*root,l,mid,x,y,p);
if (y>mid) update(2*root+1,mid+1,r,x,y,p);
up(root);
}
int query(int root,int l,int r,int x,int y)
{
if (l>r || x>y) return -1;
if (x<=l && r<=y)
{
return f[root];
}
pushdown(root,l,r);
int mid = (l+r) >> 1;
int ans=0;
if (x<=mid) ans=max(ans,query(2*root,l,mid,x,y));
if (y>mid) ans=max(ans,query(2*root+1,mid+1,r,x,y));
return ans;
}
bool check(int min1,int max1)
{
build(1,1,n);
memset(f,-1,sizeof(f));
memset(dp,-1,sizeof(dp));
int l=1,r=1;
//int beg=1;
dp[1]=0;
//cout<<1<<endl;
for (int i=2;i<=n+1;i++)
{
//cout<<1<<endl;
while (x[i]-x[r] >= min1) update(1,1,n,r,r,dp[r]),r++;
while (x[i]-x[l]>max1) update(1,1,n,l,l,-1),l++;
//if (l>r) continue;
//cout<<1<<endl;
int tmp = query(1,1,n,1,n);
if (tmp!=-1)
{
dp[i]=max(dp[i],tmp+val[i]);
}
}
int mx=-1e9;
for (int i=1;i<=n+1;i++) mx=max(dp[i],mx);
//for (int i=1;i<=n+1;i++) cout<<dp[i]<<" ";
// cout<<endl;
if (mx>=k) return true;
else return false;
}
int main()
{
n=read(),d=read(),k=read();
for (int i=2;i<=n+1;i++) x[i]=read(),val[i]=read();
int l=1,r=1e9;
int ans=0;
while (l<=r)
{
int mid =(l+r) >> 1;
if (check(max(d-mid,1),d+mid)) r=mid-1,ans=mid;
else l=mid+1;
//cout<<l<<" "<<r<<endl;
}
if (ans==0) ans=-1;
cout<<ans<<endl;
return 0;
}
真正的正确做法 是使用单调队列优化
单调队列是什么呢?
是一个队列 对,队列中的元素都是单调的
就比如说\(3,2,1\)
单调队列在维护的时候有两个注意:
1.新进来元素,会将他之前连续的比他小的元素代替掉,直到遇到一个大的,或者队列为空 (可以理解为过期晚,决策更优秀)
2.注意\(head\)和\(tail\)的大小和加减
直接上代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 1000010;
ll x[maxn],val[maxn];
ll n,d,k;
ll dp[maxn];
ll q[maxn];
ll t[maxn];
ll l=1,r=1;
ll head=1,tail=0;
void push(int now,ll min1,ll max1)
{
while (x[now]-x[r]>=min1)
{
while (dp[r]>q[tail] && head<=tail) q[tail]=t[tail]=0,tail--;
q[++tail]=dp[r];
t[tail]=x[r];
r++;
}
while (x[now]-t[head]>max1 && head<=tail) t[head]=0,q[head++]=0;
}
bool check(ll min1,ll max1)
{
memset(dp,0xdf,sizeof(dp));
l=1,r=1;
dp[1]=0;
head=1,tail=0;
for (int i=2;i<=n+1;++i)
{
push(i,min1,max1);
if (head<=tail)
{
dp[i]=max(dp[i],q[head]+val[i]);
}
}
ll mx=-1e9;
for (int i=1;i<=n+1;i++) mx=max(dp[i],mx);
if (mx>=k) return true;
else return false;
}
int main()
{
n=read(),d=read(),k=read();
for (int i=2;i<=n+1;i++) x[i]=read(),val[i]=read();
ll l1=1,rr=6e9;
ll ans=0;
while (l1<=rr)
{
ll mid =(l1+rr) >> 1;
if (check(max(d-mid,(long long)1),d+mid)) rr=mid-1,ans=mid;
else l1=mid+1;
//cout<<l<<" "<<r<<endl;
}
if (ans==0) ans=-1;
cout<<ans<<endl;
return 0;
}