可以把題目理解爲在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;