題目描述
小藍要在自己的生日宴會上將 \(n\) 包糖果分給 \(m\) 個小朋友。每包糖果都要分出去,每個小朋友至少要分一包,也可以分多包。
小藍已經提前將糖果准備好了,為了在宴會當天能把糖果分得更平均一些,小藍要先計算好分配方案。 小藍將糖果從 \(1\) 到 \(n\) 編號,第 \(i\) 包糖果重 \(w_i\)。小朋友從 \(1\) 到 \(m\) 編號。每個小朋友只能分到編號連續的糖果。小藍想了很久沒想出合適的分配方案使得每個小朋友分到的糖果差不多重。因此需要你幫他一起想辦法。為了更好的分配糖果,他可以再買一些糖果,讓某一些編號的糖果有兩份。當某個編號的糖果有兩份時,一個小朋友最多只能分其中的一份。
請找一個方案,使得小朋友分到的糖果的最大重量和最小重量的差最小,請輸出這個差。
評測用例規模與約定
對於所有評測用例,\(1 \leq n \leq 100,1 \leq m \leq 50,1 \leq w_i \leq 100\)。在評測數據中,\(w_i\) 隨機生成,在某個區間均勻分布。
題解
考慮 \(DP\)。
大概可以列出這五個維度:分出的區間個數,最后一個用了一顆糖的位置,最后一個用了兩顆糖的位置,區間和的最小值,區間和的最大值。
考慮把其中一維拿出來作為 \(DP\) 維護的值,這一維必須滿足的條件是滿足越大越優或者越小越優的單調性。
只有最小值和最大值滿足單調性。此時很關鍵的一點,我們發現最小值有上界 \(\frac {2\cdot sum}{m}\),使得時間復雜度有顯著的優化,而最小值有一個不太重要的下界,因此我們枚舉最小值,把最大值作為 \(DP\) 維護的值。
規划 \(DP\)
令 \(sum=\sum w_i\),規划 \(DP\):\(f_{i,j,k}\) 表示前 \(i\) 個區間,最后一個用了一顆糖的位置在 \(j\),最后一個用了兩顆糖的位置在 \(k\)(在 \(k\) 之前也行),最大值最小是多少。考慮 \(3\) 種轉移:
-
\(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,j,k'},s_k-s_{k'})\),要求 \(j\ge k,k'\),意思是放在第二段,還沒有超出原來第一段的范圍。
然而這種轉移(區間互相包含)一定不優。 -
\(f_{i,j,k}\leftarrow \max_{}(f_{i-1,k,j'},s_k-s_{j'})\),要求是 \(j'\le k\le j\)
轉移是 \(O(n)\) 的,注意轉移合法的要求是新的一段和 \(\ge mi\)
-
區間包含不優證明
為什么區間互相包含一定不優?我們可以從調整的角度來想這個問題。首先構思一個大區間,一個小區間,還有一個區間位於小區間的右邊,但與大區間存在一部分重疊,稱這個區間為右區間。
第一個結論:小區間和右區間之間不應該存在間隔。很顯然小區間右端點調整到右區間的左邊會更優。
而當小區間和右區間之間沒有間隔后,我們就可以把大區間的右端點調整為小區間的右端點,顯然這樣大區間的和會更接近於小區間的和,使得結果更優。
如果沒有右區間,同理我們可以將小區間的右端點調整到大區間的右端點。
考慮這樣做的復雜度,實際上是 \(O(\frac {2\cdot sum}{m}×n^3m)=O(sum\cdot n^3)\)。
優化
-
對於第三類轉移,不難發現 \(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;
}