【牛客网】货物分组

dp+决策单调性

part1.30pts

显然,前30分是很好写的。

\(dp[i][j]\)表示前i个箱子分成了j个组的最小代价。

复杂度:\(O(n^3)\)

代码:

namespace p30{
    ll dp[510][510],ans=2e18+7;
    void work(){
        memset(dp,63,sizeof(dp));
        dp[0][0]=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=i;j++){
                int mx=A[i],mi=A[i];
                for(int k=i-1;k>=0;k--){
                    if(sum[i]-sum[k]>W)break;
                    dp[i][j]=min(dp[i][j],0LL+dp[k][j-1]+1LL*(sum[i]-sum[k])*j+mx-mi);
                    mx=max(mx,A[k]);
                    mi=min(mi,A[k]);
                }
            }
        }
        for(int i=1;i<=n;i++)ans=min(ans,dp[n][i]);
        cout<<ans;
    }
}

part2.60pts

对于这部分费用:

对于第i个箱子,如果里面装的货物总重量为w,那么费用为i*w 。

我们转化一下,假设有5个物品,编号为1,2,3,4,5。其中装的箱子为{1,2},{3},{4,5}。

那么上述费用就是:\(A[1]\cdot 1+A[2]\cdot 1+A[3]\cdot2+A[4]\cdot3+A[5]\cdot3\),也可以写成\((A[1]+A[2]+A[3]+A[4]+A[5])+(A[3]+A[4]+A[5])+(A[4]+A[5])\)

这样,当我们考虑到第i个物品时,不管第i+1个到第n个怎么取,最终答案至少要多花费\(\sum_{j=i+1}^{j\le n}A[j]\)

因此,我们设\(dp[i]\)表示考虑前i个物品时,最终花费的最小总代价。

转移方程:

\[dp[0]=\sum_{k=1}^{k \le n}A[k] \]

\[dp[i]=min\{dp[j]+max(j+1,i)-min(j+1,i)\}+\sum_{k=i+1}^{k\le n}A[k]   j\in[0,i-1],\sum_{k=j+1}^{k\le i}A[k]\le W \]

其中\(max(j+1,i)\)表示j+1到i中A的最大值,\(min(j+1,i)\)则表示最小值。

复杂度:\(O(n^2)\)

代码:

namespace p60{
    ll dp[MAXN],ans=2e18+7;
    void work(){
        memset(dp,63,sizeof(dp));
        dp[0]=sum[n];
        for(int i=1;i<=n;i++){
            int mx=A[i],mi=A[i];
            for(int j=i-1;j>=0;j--){
                if(sum[i]-sum[j]>W)break;
                dp[i]=min(dp[i],0LL+dp[j]+mx-mi+sum[n]-sum[i]);
                mx=max(mx,A[j]);
                mi=min(mi,A[j]);
            }
        }
        cout<<dp[n];
    }
}

part3.100pts

对于\(max(j+1,i)\)\(min(j+1,i)\)可以用单调栈维护。

由于\(max(j+1,i)\)\(min(j+1,i)\)是不断变化的,所以我们需要开一棵线段树维护。

复杂度:\(O(n\cdot logn)\)

具体细节请看代码吧。

代码:

namespace p100 {
    int stkMx[MAXN],stkMi[MAXN],topMx,topMi;
    struct sgt {
        int L,R;
        ll sum,lazy;
    } tree[MAXN<<2];
    ll dp[MAXN];
    void Down(int p) {
        if(tree[p].lazy==0)return;
        tree[p<<1].sum+=tree[p].lazy,tree[p<<1|1].sum+=tree[p].lazy;
        tree[p<<1].lazy+=tree[p].lazy,tree[p<<1|1].lazy+=tree[p].lazy;
        tree[p].lazy=0;
        return;
    }
    void Up(int p) {
        tree[p].sum=min(tree[p<<1].sum,tree[p<<1|1].sum);
    }
    void build(int L,int R,int p) {
        tree[p].L=L,tree[p].R=R;
        if(L==R) {
            tree[p].sum=2e18+7;
            return;
        }
        int mid=(L+R)>>1;
        build(L,mid,p<<1);
        build(mid+1,R,p<<1|1);
        Up(p);
    }
    void update1(int pos,int p,ll x) {
        if(tree[p].L==tree[p].R) {
            tree[p].sum=x;
            return;
        }
        Down(p);
        int mid=(tree[p].L+tree[p].R)>>1;
        if(pos<=mid)update1(pos,p<<1,x);
        else update1(pos,p<<1|1,x);
        Up(p);
    }
    void update2(int L,int R,int p,int x) {
        if(tree[p].L==L&&tree[p].R==R) {
            tree[p].sum+=x;
            tree[p].lazy+=x;
            return;
        }
        Down(p);
        int mid=(tree[p].L+tree[p].R)>>1;
        if(R<=mid)update2(L,R,p<<1,x);
        else if(L>=mid+1)update2(L,R,p<<1|1,x);
        else update2(L,mid,p<<1,x),update2(mid+1,R,p<<1|1,x);
        Up(p);
    }
    ll Query(int L,int R,int p) {
        if(tree[p].L==L&&tree[p].R==R)return tree[p].sum;
        Down(p);
        int mid=(tree[p].L+tree[p].R)>>1;
        if(R<=mid)return Query(L,R,p<<1);
        else if(L>=mid+1)return Query(L,R,p<<1|1);
        else return min(Query(L,mid,p<<1),Query(mid+1,R,p<<1|1));
    }
    void work() {
        build(1,n+1,1);
        dp[0]=sum[n];
        update1(1,1,dp[0]);//我这里是有便宜的,线段树上[i,i]对应的是dp[i-1]+mx-mi
        for(int i=1; i<=n; i++) {
            while(topMi&&A[i]<A[stkMi[topMi]]) {
                int now=stkMi[topMi],la=stkMi[topMi-1];
                update2(la+1,now,1,A[stkMi[topMi]]);//把原先减掉的mi给加回来(回溯)
                topMi--;
            }
            update2(stkMi[topMi]+1,i,1,-A[i]);//把现在的mi给减过去(更新)
            stkMi[++topMi]=i;
            while(topMx&&A[i]>A[stkMx[topMx]]) {
                int now=stkMx[topMx],la=stkMx[topMx-1];
                update2(la+1,now,1,-A[stkMx[topMx]]);//把原先加上的mx给减掉(回溯)
                topMx--;
            }
            update2(stkMx[topMx]+1,i,1,A[i]);//把现在的mx给加过去(更新)
            stkMx[++topMx]=i;
            int l=0,r=i-1,res;
            while(l<=r){//二分找能转移到的最左端,即满足res到i这一段和刚好小于等于W
                int mid=(l+r)>>1;
                if(sum[i]-sum[mid]<=W)res=mid,r=mid-1;
                else l=mid+1;
            }
            dp[i]=0LL+Query(res+1,i,1)+sum[n]-sum[i];
            update1(i+1,1,dp[i]);
        }
        cout<<dp[n];
    }
}
posted @ 2019-11-03 11:08  TieT  阅读(284)  评论(0编辑  收藏  举报