货物分组
题解
我们可以先分析各个分数段的代码。
10分:直接爆搜出结果。时间复杂度
30分:考虑dp。指从前j个节点分i包的最小值,通过线段树维护区间最值。时间复杂度
60分:还是dp。指前i个已经分了若干包时整个序列的最小值。
,是后缀和。时间复杂度
100分:我们发现我们可以用一个单调栈来维护最大值与最小值。时间复杂度
源码
#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;
}