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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章