D - Minimum Width Editorial by en_translator
Consider the following subproblem:
At least how many lines are required to fit the sentence into a window of width \(W\)? Here, \(L _ i\leq W\)?
It is optimal to place words greedily, successively from the initial word (as many words in the same line as possible).
Proof
Suppose that there is an optimal solution that has a newline at an unnecessary position. If that words and the succeeding ones can be fit into \(k\) lines, they fits into \(k\) lines without the newline.
Therefore, it has been shown that there is an optimal solution that does not have a newline at an unnecessary position.
Therefore, the subproblem can be solved in an \(O(N)\) time.
Let \(f(W)\) be the answer to this subproblem; then the answer to the original problem is the minimum \(W\) with \(f(W)\leq M\).
Since \(f\!\left(-1+\sum _ i(1+L _ i)\right)=1\), the answer is between \(\max _ iL _ i\) and \(-1+\sum _ i(1+L _ i)\), inclusive.
Since \(f(W)\) is a monotonically decreasing function, the problem has been solved with a binary search.
The time complexity is \(O(N\log\sum L _ i)\).
A sample code follows. The implementation might be simpler by prepending a space at the initial position of every word, so that there is no need to add a space between words. (Note that the answer exactly differs by one in this solution.)
#include <vector>
#include <algorithm>
#include <iostream>
#include <ranges>
#include <numeric>
int main() {
using namespace std;
int N, M;
cin >> N >> M;
vector<long> L(N);
for (int i = 0; i < N; ++i) {
cin >> L[i];
++L[i]; // Prepend a space
}
long lower = ranges::max(L) - 1; // The answer is at least max L[i]
long upper = reduce(begin(L), end(L)); // The answer is at most ∑ L[i]
// Binary search
while (lower + 1 < upper) {
long middle = (lower + upper) / 2;
int rows = 1; // Index of current line
long length = 0; // Position in the line
for (int i = 0; i < N; ++i) {
length += L[i]; // Try to append to the end of the line
if (length > middle) { // If it sticks out
++rows; // add a newline
length = L[i]; // and append to the beginning
}
}
if (rows > M) // If the height is large enough
lower = middle; // then the answer is strictly greater than middle;
else // 足りていれば
upper = middle; // otherwise, the answer is less than or equal to middle
}
// Print the answer subtracted by one
cout << upper - 1 << endl;
return 0;
}
In C++20 and later, one can pass ranges::iota_view
and the decision function to the std::ranges::partition_point
function to do the binary search.
When using this function, the decision function must evaluate to true
for the initial elements, and false
for the remaining.
#include <iostream>
#include <vector>
#include <algorithm>
#include <ranges>
#include <numeric>
int main() {
using namespace std;
unsigned N, M;
cin >> N >> M;
vector<unsigned long> L(N);
for(auto&& l : L){
cin >> l;
++l; // Prepend a space
}
// Decision function of the binary search
// It returns true when the answer is strictly greater than W
// => the answer is the minimum value that evaluates to false
const auto check{[L, M](unsigned long W) -> bool {
unsigned long length = 0;
unsigned rows = 1;
for (const auto l : L) {
length += l;
if (length > W) {
++rows;
length = l;
}
}
return rows > M;
}};
// ranges::partition_point(r, f) returns the first element within the range r for which f returns false
// (Here, r must be partitioned by f, i.e. every element for which f evaluates to true must be prior to an element that evaluates to false).
cout << *ranges::partition_point(views::iota(ranges::max(L), reduce(begin(L), end(L))), check) - 1 << endl;
return 0;
}
posted:
last update: