可以把题目理解为在n本书中”切几刀”.
当n<=2000时就是最裸的一维DP,O(n2)可以解决:
dp[i]表示在前i本书,第i本书为当前书架的最后一本书的最小高度.dp[i]=min{dp[j]+mx[j+1,i]}且sum[j+1,i]<=L.
当n<=100000时怎么办呢?通过打表我发现dp值是不递减的!
通过dp[i]=min{dp[j]+mx[j+1,i]},当mx[j+1,i]为h[i]时,j越小越优.假设在i前面,第一本比i高的书下标为x,如果dp值在[x+1,i-1]转移,一定选择dp[x+1]来转移.(厚度允许的情况下).如果在其他区间转移呢?
假设当前的最大值为h[x],那么dp[k]一定为k~i-1中dp值最小的,而k是比也是在x左边比它高的第一本书y前的第一本书.那么贪心的想法就有了:
每一本书a都”管理”它前面比它小的一段
区间,如果以a为当前书架的最高值,那么就要尽可能选取这个区间靠近左端点的点来转移.
那么对于第i本书:
dp[i]=min{dp[pre[a]]+h[a]}//sum[pre[a]+1,i]<=L
pre[a]表示在a左边,第一本比a高的书的下标.
那么只要用堆来维护dp[pre[a]]+h[a],单调栈来找pre[a]即可.
注意:
① 如果a后出现了比它高的书,那么以a为最高值的状态就是无效的了.
② 别忘了题目中的厚度限定条件,当遇到sum值过大时,应当修改pre[a].
③ 答案要longlong.
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=2005;
const int oo=1e9+5;
int dp[M],mx[M][M],n,L,sum[M],h[M];
int main(){
int i,j,k,x;
scanf("%d %d",&n,&L);
for(i=1;i<=n;i++){
scanf("%d %d",&h[i],&x);
sum[i]=sum[i-1]+x;
}
for(i=1;i<=n;i++){
for(j=i;j<=n;j++)mx[i][j]=max(mx[i][j-1],h[j]);
}
for(i=1;i<=n;i++){
dp[i]=oo;
for(j=i-1;j>=0;j--){//[j+1,i]
if(sum[i]-sum[j]>L)break;
dp[i]=min(dp[i],dp[j]+mx[j+1][i]);
}
}
printf("%d\n",dp[n]);
return 0;
}
n<=100000:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
const int M=100005;
const int oo=1e9+5;
int n,L,h[M],vstk[M],pre[M],mark[M],w[M];
struct node{
int x;
ll v,tot;
bool operator<(const node &tmp)const{
return v>tmp.v;//小顶堆
}
};
ll dp[M],sum[M]={0};
priority_queue<node>Q;
int main(){
int ok=1,i,j,k,x,top=0;
scanf("%d %d",&n,&L);
for(i=1;i<=n;i++){
scanf("%d %d",&h[i],&w[i]);
if(h[i]<h[i-1])ok=0;//sum[i]=sum[i-1]+x;
}
if(ok){//这里其实是个坑:上述算法遇到一个递减序列就会失效,所以为了防止这种情况可以把序列倒过来.
for(i=1;i<=n/2;i++){
swap(h[i],h[n-i+1]);
swap(w[i],w[n-i+1]);
}
}
for(i=1;i<=n;i++)sum[i]=sum[i-1]+w[i];
h[0]=oo;
vstk[++top]=0;
for(i=1;i<=n;i++){
while(top>=1&&h[vstk[top]]<=h[i]){
mark[vstk[top]]=1;
top--;//表示第一个比i大的数
}
pre[i]=vstk[top];//第一个比我大的数
vstk[++top]=i;
while(sum[i]-sum[pre[i]]>L)pre[i]++;//[pre[i]+1,i]
dp[i]=dp[pre[i]]+h[i];
while(!Q.empty()){
node a=Q.top();
if(mark[a.x]){Q.pop();continue;}
if(sum[i]-a.tot<=L){
dp[i]=min(a.v,dp[i]);break;
}
else if(sum[i]-sum[a.x-1]<=L){
Q.pop();
int t;
for(j=pre[a.x]+1;j<=a.x;j++){
if(sum[i]-sum[j-1]<=L){
t=j-1;break;
}else mark[j]=1;
}
pre[a.x]=j-1;
Q.push((node){a.x,dp[t]+h[a.x],sum[t]});
}
else Q.pop();//a.x到i的厚度已经超过L,那么这个状态就无效了
}
Q.push((node){i,dp[pre[i]]+h[i],sum[pre[i]]});
}
cout<<dp[n]<<endl;
return 0;