蓝桥杯 - 分果果
题目描述
小蓝要在自己的生日宴会上将
小蓝已经提前将糖果准备好了,为了在宴会当天能把糖果分得更平均一些,小蓝要先计算好分配方案。 小蓝将糖果从
请找一个方案,使得小朋友分到的糖果的最大重量和最小重量的差最小,请输出这个差。
评测用例规模与约定
对于所有评测用例,
题解
考虑
大概可以列出这五个维度:分出的区间个数,最后一个用了一颗糖的位置,最后一个用了两颗糖的位置,区间和的最小值,区间和的最大值。
考虑把其中一维拿出来作为
只有最小值和最大值满足单调性。此时很关键的一点,我们发现最小值有上界
设计 DP 转移
令
-
f_{i,j,k}\leftarrow f_{i,j,k-1} -
第
i 个人用了上一次的j+1 开始的一段,这种转移是f_{i,j,k}\leftarrow \max(f_{i-1,j',k},s_j-s_{j'}) -
第
i 个人用了上一次的k+1 开始的一段,这种转移有两类:-
然而这种转移(区间互相包含)一定不优。 -
f_{i,j,k}\leftarrow \max_{}(f_{i-1,k,j'},s_k-s_{j'})$,要求是 $j'\le k\le j 转移是
O(n) 的,注意转移合法的要求是新的一段和\ge mi 区间包含不优证明
为什么区间互相包含一定不优?我们可以从调整的角度来想这个问题。首先构思一个大区间,一个小区间,还有一个区间位于小区间的右边,但与大区间存在一部分重叠,称这个区间为右区间。
-
第一个结论:小区间和右区间之间不应该存在间隔。很显然小区间右端点调整到右区间的左边会更优。
而当小区间和右区间之间没有间隔后,我们就可以把大区间的右端点调整为小区间的右端点,显然这样大区间的和会更接近于小区间的和,使得结果更优。
如果没有右区间,同理我们可以将小区间的右端点调整到大区间的右端点。
考虑这样做的复杂度,实际上是
优化
-
对于第三类转移,不难发现
f_{i,j,k} 是随着k 增加而单减的,那么选取尽量靠后的转移点一定最优。 -
对于第二类转移,考虑单调栈优化。具体地说,由于
s_j-s_{j'} 是随j' 增加而单调递减的,我们需要的是min\{s_j-s_{j'},f_{i-1,j',k}\} 最小,那么当s_j-s_{j'} 变大时,若f_{i-1,j',k} 也变大,那么这样的点j' 可以舍弃,符合单调栈的原则。而我们发现,随着j 的增加,s_j-s_{j'} 的图像是向上平移的,这将会导致最佳转移点的右移;而f 的图像只能被“往下压”,在其长度不变短的情况下,也会导致最佳转移点的右移。因此我们维护这个最佳转移点即可,值得注意的是最佳转移点可能是相邻的两点。Code
有点卡时,在洛谷上开
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 51, N = 101;
int n, m, w[N], f[M][N][N], st[N], ans = 0x7fffffff;
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
scanf("%d", &w[i]);
w[i] += w[i - 1];
}
for(int minw = 1; minw <= 2 * w[n] / m; minw++){
memset(f, 0x7f, sizeof(f));
f[0][0][0] = minw;
for(int i = 1; i <= m; i++) {
for(int j = 0; j <= n; j++){
*st = 0;
int p = 0, pst = 1;
for(int k = j; k <= n; k++){
if(j > 0) f[i][j][k] = f[i][j - 1][k];
while(w[k] - w[p] >= minw){
if(p >= j){
while(*st && f[i - 1][j][p] <= f[i - 1][j][st[*st]])
(*st)--;
st[++*st] = p;
}
p++;
}
if(*st){
pst = min(pst, *st);
while(pst < *st && f[i - 1][j][st[pst + 1]] < w[k] - w[st[pst + 1]])
pst++;
while(pst > 1 && f[i - 1][j][st[pst]] > w[k] - w[st[pst]])
pst--;
f[i][j][k] = min(f[i][j][k], max(f[i - 1][j][st[pst]], w[k] - w[st[pst]]));
if(pst < *st)
f[i][j][k] = min(f[i][j][k], max(f[i - 1][j][st[pst + 1]], w[k] - w[st[pst + 1]]));
}
int pp = min(p - 1, j);
if(pp >= 0)
f[i][j][k] = min(f[i][j][k], max(f[i - 1][pp][j], w[k] - w[pp]));
}
}
}
ans = min(ans, f[m][n][n] - minw);
}
printf("%d", ans);
return 0;
}