數列分段 Section II
連結:https://www.luogu.com.cn/problem/P1182
題目描述
對於給定的一個長度為 \(N\) 的正整數數列 \(A_{1\sim N}\),現要將其分成 \(M\)(\(M\leq N\))段,並要求每段連續,且每段和的最大值最小。
關於最大值最小:
例如一數列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段。
將其如下分段:
\[[4\ 2][4\ 5][1]
\]
第一段和為 \(6\),第 \(2\) 段和為 \(9\),第 \(3\) 段和為 \(1\),和最大值為 \(9\)。
將其如下分段:
\[[4][2\ 4][5\ 1]
\]
第一段和為 \(4\),第 \(2\) 段和為 \(6\),第 \(3\) 段和為 \(6\),和最大值為 \(6\)。
並且無論如何分段,最大值不會小於 \(6\)。
所以可以得到要將數列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段,每段和的最大值最小為 \(6\)。
輸入格式
第 \(1\) 行包含兩個正整數 \(N,M\)。
第 \(2\) 行包含 \(N\) 個空格隔開的非負整數 \(A_i\),含義如題目所述。
輸出格式
一個正整數,即每段和最大值最小為多少。
樣例 #1
樣例輸入 #1
5 3
4 2 4 5 1
樣例輸出 #1
6
提示
對於 \(20\%\) 的資料,\(N\leq 10\)。
對於 \(40\%\) 的資料,\(N\leq 1000\)。
對於 \(100\%\) 的資料,\(1\leq N\leq 10^5\),\(M\leq N\),\(A_i < 10^8\), 答案不超過 \(10^9\)。
解答
- 用二分答案的思想:這個其實我想到了,但是沒法做,當時沒思路
- 忽略了用段數來反映區間和
- 也就是當前最大值,判斷最多構成多少段,如果當前段數比預期小,說明我們選的最大值太大了,如果段數過大,說明我們選的最大值過大了
- 注意左端點取值,從一個數的最大值開始,也就是將它劃分為一段,不能從 0 開始,會被 hack
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N];
int n, m;
bool check(int x)
{
int cnt = 0;
LL sum = 0;
for (int i = 0; i < n; i++)
{
if (sum + a[i] <= x) sum += a[i];
else sum = a[i], cnt++;
}
// 注意這個判斷
// 這裡取小於,不可取等,因為段數等,可能還有更好的數,過小的,說明數過大
// 但是這裡注意不用減減,因為二分只需保證一個加加或減減,否則用以 TLE
// 為啥不用考慮,因為下面 else 將這裡也考慮了,
// 就算段數等,也讓它加加,找到段數等最大的數,這裡 r 不變就體現處作用了
if (cnt < m) return true;
else return false;
}
int main()
{
cin >> n >> m;
int l = 0, r = 1e9;
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
l = max(l, a[i]);
}
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l;
return 0;
}
說明BUG
input
5 4
1 2 3 4 5
output
5
- 上述是一個被 hack 的資料,也就是為啥不從 0 或 1 開始,左端點
- 當時想著雖然現在小,但是不會逐漸增加嗎
- 實際有問題,增加也就是走了 else ,但是存在不走
- 也就是前面足夠小,讓段數減的慢,恰好讓後面每個數都成為了單獨的一段,而這單獨的一段又比 mid 大
- 這就是問題所在了,會讓輸出比實際輸出小