货物分组

货物分组

题解

我们可以先分析各个分数段的代码。

10分:直接爆搜出结果。时间复杂度O(n!)

30分:考虑dp。dp_{i,j}指从前j个节点分i包的最小值,通过线段树维护区间最值。时间复杂度O(n^{3}*log_{n})

60分:还是dp。dp_{i}指前i个已经分了若干包时整个序列的最小值。

           dp_{i}= dp_{j}+suf_{j+1}+g(j+1,i)suf是后缀和。时间复杂度O(n^{2}log_{n})

100分:我们发现我们可以用一个单调栈来维护最大值与最小值。时间复杂度O(nlog_{n})

源码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<stack>
#include<vector>
#include<queue>
#define MAXN 100005
using namespace std;
typedef long long LL;
const LL INF=0x7f7f7f7f7f;
LL n,W,a[MAXN],suf[MAXN];
LL dp[MAXN],ans;
LL s[MAXN],lg[MAXN];
LL Max[MAXN][25],Min[MAXN][25];
#define gc() getchar()
template<typename _T>
inline void read(_T &x)
{
    _T f=1;x=0;char s=gc();
    while(s>'9'||s<'0'){if(s=='-')f=-1;s=gc();}
    while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=gc();}
    x*=f;
}
LL queryMax(int l,int r)
{
    LL tmp=lg[r-l+1];
    return max(Max[l][tmp],Max[r-(1<<tmp)+1][tmp]);
}
LL queryMin(int l,int r)
{
    LL tmp=lg[r-l+1];
    return min(Min[l][tmp],Min[r-(1<<tmp)+1][tmp]);
}
inline LL getDp(int j,int i)
{
    //printf("%d %d:%lld %lld\n",j,i,queryMin(j+1,i),queryMax(j+1,i));
    return dp[j]-queryMin(j+1,i)+queryMax(j+1,i)+suf[j+1];
}
int main()
{
    for(int i=0;(1<<i)<MAXN;i++) lg[1<<i]=i;//求log值
    for(int i=1;i<MAXN;i++) lg[i]=max(lg[i],lg[i-1]);
    read(n);read(W);
    for(int i=1;i<=n;i++)
    {
        read(a[i]);
        suf[i]=a[i];
        Max[i][0]=Min[i][0]=a[i];
    }
    for(int i=n;i>0;i--)
        suf[i]+=suf[i+1];//求后缀和
    for(LL j=1;j<=20;j++)
        for(LL i=1;i<=n;i++)
        {
            Max[i][j]=max(Max[i][j-1],Max[min(i+(1<<(j-1)),n)][j-1]);
            Min[i][j]=min(Min[i][j-1],Min[min(i+(1<<(j-1)),n)][j-1]);
        }//维护最大值与最小值
    int pos=0;
    for(LL i=1;i<=n;i++)
    {
        while(suf[pos+1]-suf[i+1]>W) pos++;//排除总和大于W的
        while(pos<i-1&&getDp(pos,i)>=getDp(pos+1,i)) pos++;//维护当前最大值
        dp[i]=getDp(pos,i);//dp
        for(int j=1;j<=500&&pos>=j&&suf[pos-j+1]-suf[i+1]<=W;j++) dp[i]=min(getDp(pos-j,i),dp[i]);
        for(int j=1;j<=500&&pos+j<i;j++) dp[i]=min(getDp(pos+j,i),dp[i]);
    }
    printf("%lld",dp[n]);
    return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值