USACO2011Open Gold Bookshelf 题解

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


发布了39 篇原创文章 · 获赞 2 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章