「牛客」货物分组
题意简述
n 个物品,第 i 个物品重量为 ai ,要求按顺序连续放入若干箱子,每个箱子物品总重不超过 W ,箱子从 1 开始按序编号,对于第 i 个箱子 ,其代价为
i×(箱中货物总重)+(箱中最大货物重量)−(箱中最小货物重量)
求最小总代价
注:数据范围
对于 10% 的数据,n≤10
对于 30% 的数据,n≤500
对于 60% 的数据,n≤5000
对于 100% 的数据,n≤105,1≤ai≤W≤105
题解
30pts
先记 sn=∑i=1nai
考虑暴力 dp,设状态 fi,j 表示前 i 个物品当前分到第 j 个箱子的最小代价,考虑转移:
fi,j=k<i,si−sk≤Wminfk,j−1+j⋅(si−sk)+cost(k+1,i)
其中 cost(k+1,i) 计算 ak+1,ak+2,…,ai 这段的极差(最大值减最小值),用 ST 表可以维护
上述 dp 状态 O(n2) ,转移 O(n) 显然不够优秀,可以把状态优化掉一维。
60pts
可以 费用提前计算 ,去掉原本 j 这维,转移方程如下:
fi=j<iminfj+sn−sj+cost(j+1,i)
复杂度 O(n2)
100pts
唯一可以优化的就是转移了,fj+sn−sj 是定值,唯一烦人的是 cost ,cost 确实有单调性,但是和 fj+sn−sj 加起来就不一定了。
记 gj=fj+sn−sj+cost(j+1,i) ,再线段树中维护 g ,然后我们要关心的是当 i 变成 i+1 时这些 cost(j+1,i) 会怎么变:
cost(j+1,i)=j+1≤p≤imaxap−j+1≤p≤iminap 显然需要分开考虑,为了方便,这里我们先讨论 max 。 首先 j+1≤p≤imaxap 显然随 j 单调递减(非严格递减),形象一点画出来大概这样:(其中 r5 就等于 i)

现在处理 ai+1 的影响(看下图),显然 4,5 两段最大值会更新到 ai+1 ,实际在线段树中的操作为 [l5,r5] 区间加 ai+1−c5 ,[l4,r4] 区间加 ai+1−c4 。

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

图上的东西用个单调栈维护即可,记录相同值的区间左右端点和当前 max 值,更新时维护单调栈,并在线段树中更新最值。min 的维护同理,无非是单调递增罢了。
然后是 dp 的更新 fi=querymin(Lp,i−1) ,querymin 是在线段树中查询区间最值,Lp 是满足 si−sLp 的最小值,不符合条件不断向右移就行了。
计算一下复杂度,线段树修改和查询都是 logn ,查询 n 次数 ,修改次数为即为单调栈中区间合并次数,故也是 O(n) 的。
故复杂度 O(nlogn)
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; }
   |