洛谷P1848 [USACO12OPEN]书架Bookshelf
https://www.luogu.org/problem/show?pid=1848
这道题目的线段树简直就是水题,然后用set去做,日了狗了,搞了1个小时左右结果只过4个点,标算又看不懂,先把坑挖好;
首先这道题目的暴力dp那是显然的
f[i]可以从f[j-1]+max(h[j]……h[i])推得
不难发现无论是f[]还是当j确定的max(h[j]……h[i]),都是单调的;
那么我们大力线段树;
对于一个新加入的i点
简单来说就是维护h[j]=max(h[j]……h[i]),顺便吧答案维护了;
然后单调队列获得一段答案的可行区间last~i
那么在这段区间里取答案;
最后把答案放到f[i+1]
换句话说一开始的f[1]是0
这样的话每一个叶子节点的答案刚刚好就是f[j]+h[j]
#include<bits/stdc++.h>
#define Ll long long
using namespace std;
const int N=1e5+5;
struct tree{int l,r,h,H,lazy;Ll f,v;}T[N*4];
int q[N],last;
int n,L,x,y;
Ll sum,ans;
void make(int id,int l,int r){
T[id].l=l;T[id].r=r;
if(l==r)return;
int mid=l+r>>1;
make(id*2 ,l,mid );
make(id*2+1,mid+1,r);
}
void push(int id){
if(T[id].lazy==0||T[id].l==T[id].r)return;
int x=id*2,y=x+1;
T[x].H=T[x].h=T[y].H=T[y].h=T[x].lazy=T[y].lazy=T[id].lazy;
T[x].v=T[x].f+T[x].H;T[y].v=T[y].f+T[y].H;T[id].lazy=0;
}
void change(int id,int l,int r,int x){
push(id);
if(l<=T[id].l&&T[id].r<=r){
if(T[id].H<x){
T[id].H=T[id].h=T[id].lazy=x;
T[id].v=T[id].f+x;
}else{
if(T[id*2 ].h<x)change(id*2 ,l,r,x);
if(T[id*2+1].h<x)change(id*2+1,l,r,x);
T[id].H=max(T[id*2].H,T[id*2+1].H);
T[id].h=min(T[id*2].h,T[id*2+1].h);
T[id].v=min(T[id*2].v,T[id*2+1].v);
}return;
}
if(T[id*2 ].r>=l)change(id*2 ,l,r,x);
if(T[id*2+1].l<=r)change(id*2+1,l,r,x);
T[id].H=max(T[id*2].H,T[id*2+1].H);
T[id].h=min(T[id*2].h,T[id*2+1].h);
T[id].v=min(T[id*2].v,T[id*2+1].v);
}
Ll out(int id,int l,int r){
push(id);
if(l<=T[id].l&&T[id].r<=r){
// if(l==4&&r==5)cout<<id<<' '<<T[id].h<<endl;
return T[id].v;
}
Ll ans=1e18;
if(T[id*2 ].r>=l)ans=min(ans,out(id*2 ,l,r));
if(T[id*2+1].l<=r)ans=min(ans,out(id*2+1,l,r));
return ans;
}
void add(int id,int x,Ll f){
push(id);
if(T[id].l==T[id].r){T[id].f=f;return;}
if(T[id*2].r>=x)add(id*2,x,f);else add(id*2+1,x,f);
T[id].f=min(T[id*2].f,T[id*2+1].f);
}
int main()
{
scanf("%d%d",&n,&L);
last=1;
make(1,1,n);
for(int i=1;i<=n;i++){
scanf("%d%d",&x,&q[i]);
sum+=q[i];
while(sum>L)sum-=q[last++];
change(1,1,i,x);
ans=out(1,last,i);
if(i!=n)add(1,i+1,ans);
}printf("%lld",ans);
}
那什么单调队列那我明天学习吧,真的是日了狗了
单调队列调出来了哈哈;
感觉单调队列并没有数据结构那么暴力那么好打;
这种做法完全依赖于这道题目的单调性;
首先先说几点
我的写法貌似并不是很快,而且在不开O2的情况下是比线段树还要慢点;
开了o2就是很快的啦;
然后对于这个set我每进进行一次插入删除,那么就需要一个log的时间,我的程序是带有很大的常数的,但感觉也是基本的一种做法;
对于基本的n^2的dp我就不在赘述了;
那么我们对于每一个读入i
首先就是需要确定可向左边扩展的最远点last
然后根据h[i]去更新这段区间里的h[j];
然后把last~i这段区间的答案全放到一个set里面去
最后去一个最小值产生一个新的f[i];
于是我开了一个队列q
代表现在last~i这段区间里,高度为h[j]最靠前的下标
因为当i确定的时候,h[j]>=h[j+1]
并且h[j]有很多相同的,那么我们要把h[j]合并起来
那么我们对于一个i,先把i放到q里面,然后不断找h[q[top]]<=h[i]把他弹出
之后再把h[j]<=h[i]的最靠前的重新插入,这样的话我们就可以维护关于h[j]了
然后我们考虑如何删除到不了的点,就是更新last值
首先我们判断当前的点是否在q里面,如果在的话就从队首弹出,不在的话,我们把h[j]跟新为h[j-1]
为什么呢,因为我们维护h[j]相同的一块的时候我们只是更新了h[j]值相同的最前面的一点,并且把这个点放到了q里面,那么之后的点的h[j]值其实就是h[j-1],
最后我们要看看现在的last值是不是在队列里面,如果不在的话要从队首压入;
如果没听懂那么很抱歉,我的语文水平貌似太差了
其实就是找到单调性的h[j]规律而已
至于那个in[j]就代表j有没有在q里面
#include<bits/stdc++.h>
#define Ll long long
using namespace std;
const Ll N=1e5+5;
Ll h[N],w[N],f[N],q[N],top;
bool in[N];
multiset<Ll>S;
Ll n,m,last,sum,l;
int main()
{
scanf("%lld%lld",&n,&m);
l=last=1;in[1]=1;
for(Ll i=1;i<=n;i++){
scanf("%lld%lld",&h[i],&w[i]);
sum+=w[i];
while(sum>m){
if(in[last])S.erase(f[last-1]+h[last]),l++;else h[last]=h[last-1];
in[last]=0;sum-=w[last++];
}
if(!in[last]){
in[last]=1;q[--l]=last;h[last]=h[last-1];
S.insert(f[last-1]+h[last]);
}
q[++top]=i;S.insert(f[i-1]+h[i]);in[i]=1;
while(top>=l&&h[q[top]]<=h[i]){
in[q[top]]=0;
S.erase(f[q[top]-1]+h[q[top]]);
top--;
}
in[q[++top]]=1;
h[q[top]]=h[i];
S.insert(f[q[top]-1]+h[q[top]]);
f[i]=*S.begin();
}
printf("%lld",f[n]);
}