「牛客」货物分组

题意简述

nn 个物品,第 ii 个物品重量为 aia_i ,要求按顺序连续放入若干箱子,每个箱子物品总重不超过 WW ,箱子从 11 开始按序编号,对于第 ii 个箱子 ,其代价为

i×(箱中货物总重)+(箱中最大货物重量)(箱中最小货物重量)i \times \text{(箱中货物总重)}+\text{(箱中最大货物重量)}-\text{(箱中最小货物重量)}

求最小总代价

注:数据范围
对于 10%10\% 的数据,n10n\leq 10
对于 30%30\% 的数据,n500n\leq 500
对于 60%60\% 的数据,n5000n\leq 5000
对于 100%100\% 的数据,n105,1aiW105n\leq 10^5,1\leq a_i\leq W\leq 10^5

题解

30pts

先记 sn=i=1nais_n=\sum_{i=1}^{n} a_i

考虑暴力 dp,设状态 fi,jf_{i,j} 表示前 ii 个物品当前分到第 jj 个箱子的最小代价,考虑转移:

fi,j=mink<i,siskWfk,j1+j(sisk)+cost(k+1,i)f_{i,j}=\min\limits_{k< i ,s_i-s_k\le W} f_{k,j-1}+j\cdot(s_i-s_k)+\mathrm{cost}(k+1,i)

其中 cost(k+1,i)\mathrm{cost}(k+1,i) 计算 ak+1,ak+2,,aia_{k+1},a_{k+2},\dots,a_i 这段的极差(最大值减最小值),用 ST 表可以维护

上述 dp 状态 O(n2)\mathcal{O}(n^2) ,转移 O(n)\mathcal{O}(n) 显然不够优秀,可以把状态优化掉一维。

60pts

可以 费用提前计算 ,去掉原本 jj 这维,转移方程如下:

fi=minj<i  fj+snsj+cost(j+1,i)f_i=\min\limits_{j< i} \; f_j+s_n-s_j+\mathrm{cost}(j+1,i)

复杂度 O(n2)\mathcal{O}(n^2)

100pts

唯一可以优化的就是转移了,fj+snsjf_j+s_n-s_j 是定值,唯一烦人的是 cost\mathrm{cost}cost\mathrm{cost} 确实有单调性,但是和 fj+snsjf_j+s_n-s_j 加起来就不一定了。

gj=fj+snsj+cost(j+1,i)g_j=f_j+s_n-s_j+\mathrm{cost}(j+1,i) ,再线段树中维护 gg ,然后我们要关心的是当 ii 变成 i+1i+1 时这些 cost(j+1,i)\mathrm{cost}(j+1,i) 会怎么变:

cost(j+1,i)=maxj+1piapminj+1piap\mathrm{cost}(j+1,i) = \max\limits_{j+1\le p\le i} a_p-\min\limits_{j+1\le p\le i} a_p 显然需要分开考虑,为了方便,这里我们先讨论 max\max 。 首先 maxj+1piap\max\limits_{j+1\le p\le i} a_p 显然随 jj 单调递减(非严格递减),形象一点画出来大概这样:(其中 r5 就等于 i)

现在处理 ai+1a_{i+1} 的影响(看下图),显然 4,5 两段最大值会更新到 ai+1a_{i+1} ,实际在线段树中的操作为 [l5,r5][l_5,r_5] 区间加 ai+1c5a_{i+1}-c_5[l4,r4][l_4,r_4] 区间加 ai+1c4a_{i+1}-c_4

然后我们把原本的 4,5 段合并了,顺带把 i+1i+1 的位置也算上,此时的r4=i+1c4=ai+1r_4'=i+1,c_4'=a_{i+1}

图上的东西用个单调栈维护即可,记录相同值的区间左右端点和当前 max 值,更新时维护单调栈,并在线段树中更新最值。min 的维护同理,无非是单调递增罢了。

然后是 dp 的更新 fi=querymin(Lp,i1)f_i=\mathrm{querymin}(Lp,i-1) ,querymin 是在线段树中查询区间最值,LpLp 是满足 sisLps_i-s_{Lp} 的最小值,不符合条件不断向右移就行了。

计算一下复杂度,线段树修改和查询都是 logn\log n ,查询 nn 次数 ,修改次数为即为单调栈中区间合并次数,故也是 O(n)\mathcal{O}(n) 的。

故复杂度 O(nlogn)\mathcal{O}(n\log n)

CODE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll tag[N<<2],mn[N<<2],f[N],s[N];
int n,a[N],W;
inline int red() {
int r=0;char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') r=(r<<1)+(r<<3)+(ch^48),ch=getchar();
return r;
}
void updata(int x,ll vl) {mn[x]+=vl;tag[x]+=vl;}
void pushup(int x) {mn[x]=min(mn[x<<1],mn[x<<1|1]);}
void pushdown(int x) {if(tag[x]) {updata(x<<1,tag[x]);updata(x<<1|1,tag[x]);tag[x]=0;}}
void modify(int L,int R,ll vl,int l=1,int r=n+1,int x=1) {
if(L<=l&&r<=R) { updata(x,vl);return ;}
int mid=(l+r)>>1;pushdown(x);
if(L<=mid) modify(L,R,vl,l,mid,x<<1);
if(R>mid) modify(L,R,vl,mid+1,r,x<<1|1);
pushup(x);
}
ll query(int L,int R,int l=1,int r=n+1,int x=1) {
if(L<=l&&r<=R) {return mn[x];}
int mid=(l+r)>>1;pushdown(x);ll ret=inf;
if(L<=mid) ret=query(L,R,l,mid,x<<1);
if(R>mid) ret=min(ret,query(L,R,mid+1,r,x<<1|1));
return ret;
}
struct rec {
int l,r,c;
rec(int _l,int _r,int _c):l(_l),r(_r),c(_c){}
rec(){}
};
struct sta {
rec a[N];int tot=0;
int size() {return tot;}
bool empty() {return tot==0;}
rec top() {return a[tot];}
void pop() {tot--;}
void push(rec x) {a[++tot]=x;}
}smx,smn;
int main() {
n=red(),W=red();
for(int i=1;i<=n;i++) a[i]=red(),s[i]=s[i-1]+a[i];
int Lp=0;
f[0]=0;
modify(1,1,s[n]);
for(int i=1;i<=n;i++) {
int tmpr=i,tmpl=i;
while(!smx.empty()&&smx.top().c<=a[i]) {
rec t=smx.top();smx.pop();
tmpl=t.l;
modify(t.l,t.r,a[i]-t.c);
}
smx.push(rec(tmpl,tmpr,a[i]));
tmpl=i;
while(!smn.empty()&&smn.top().c>=a[i]) {
rec t=smn.top();smn.pop();
tmpl=t.l;
modify(t.l,t.r,t.c-a[i]);
}
smn.push(rec(tmpl,tmpr,a[i]));
while(s[i]-s[Lp]>W) Lp++;
f[i]=query(Lp+1,i);
modify(i+1,i+1,f[i]+s[n]-s[i]);
}
printf("%lld\n",f[n]);
return 0;
}