#include using namespace std; constexpr int INF = 1e9; struct RMQ { int N, logN; vector> V; RMQ(const vector& vec) : N(vec.size()) { for (logN = 0; (1<(N, -INF)); copy(vec.begin(), vec.end(), V[0].begin()); for (int i = 0; i < logN; ++i) { for (int j = 0; j < N; ++j) { int m = j + (1<= r) return -INF; int p = 0; while (l + (1<<(p+1)) < r) ++p; return max(V.at(p)[l], V.at(p)[r - (1< pkgs; int time = 0; map, size_t> byval, byw, byij; void insert(int val, int par, int w, int ij) { size_t idx = pkgs.size(); pkgs.push_back(pkg{val, par, time, w, ij}); byval[{val, time}] = idx; byw[{w, time}] = idx; byij[{ij, time}] = idx; time++; } pkg* getmin() { auto it = byval.begin(); return (it == byval.end() ? nullptr : &(pkgs[it->second])); } void erase(size_t idx) { pkg p = pkgs[idx]; byval.erase(byval.find({p.val, p.intime})); byw.erase(byw.find({p.w, p.intime})); byij.erase(byij.find({p.ij, p.intime})); } void erasebyw(int K) { while (!byw.empty()) { if (byw.begin()->first.first >= K) break; erase(byw.begin()->second); } } void erasebyij(int K) { while (!byij.empty()) { if (byij.begin()->first.first >= K) break; erase(byij.begin()->second); } } }; pair> test(int L, int N, const vector& wlens, const RMQ& rmq, const vector& fgeq) { // Fix the number of rows to L. Define dp as follows: // dp[i] is the minimum width to store words [0..i] in some columns of // height at most L. Obviously we intend to then return dp[N-1]. Then: // dp[i] = min_{1 \leq j \leq L} dp[i - j] + w(i-j+1 .. i) + 1 // where w(a .. b) is the maximum width among words [a .. b]. // We maintain a set of values { dp[i-j] + w(i-j+1 .. i) } and simply // assign dp[i] to be the minimum of this set. // Note that as a function of j, w(i-j+1 .. i) is staircase shaped, and // since dp[i-j] is monotonically decreasing in j, we want to check only // those values where the maximum width is about to change, i.e. we want // those j such that w(i-j+1 .. i) != w(i-j .. i). So we maintain these // staircase points and discard those superseded by w(i+1). We also need // to check j = L separately. // // So if w(i+1) \leq w(i-j+1 .. i) then dp[i-j] + w(i-j+1 .. i) simply // becomes dp[(i+1)-(j+1)] + w((i+1)-(j+1)+1 .. i+1) without a change. But // if we have w(i+1) > w(i-j+1 .. i) then we need to find the last k such // that w(k) > w(i+1). Furthermore, we should also discard states where i-j+1 // is too far away (more than L). vector dp(N, +INF); vector par(N, -1); Candidates cands; for (int i = 0; i < N; ++i) { // Discard states that are too far away: j > L aka i - (i-j) > L. cands.erasebyij(i - L); // Discard states where w(i) > w(i-j+1 .. i-1) as described above. cands.erasebyw(wlens[i]); // For i=0..L-1 it is clearly optimal to keep everything in a single column. if (i < L) { dp[i] = rmq.query(0, i+1); par[i] = -1; } else { // Option 1: We can fill the column up entirely. dp[i] = dp[i - L] + 1 + rmq.query(i-L+1, i+1); par[i] = i - L; // Option 2: choose one of the earlier points pkg* p = cands.getmin(); if (p && p->val < dp[i]) { dp[i] = p->val; par[i] = p->par; } } // Introduce the next dp package if i+1 < N if (i+1 < N) cands.insert(dp[i] + 1 + wlens[i+1], i, wlens[i+1], i); } return {dp[N-1], par}; } int main() { int N, W; cin >> N >> W; vector S(N); vector wlens; for (auto& s : S) cin >> s, wlens.push_back(s.length()); RMQ rmq(wlens); vector fgeq(N, -1); for (int i = 1; i < N; ++i) { fgeq[i] = i-1; while (fgeq[i] >= 0 && wlens[fgeq[i]] < wlens[i]) fgeq[i] = fgeq[fgeq[i]]; } // We will binary search over L, the maximum number of lines we can use per // column. int l = 1, r = N; while (l < r) { int m = (l + r) / 2, w_for_m; std::tie(w_for_m, std::ignore) = test(m, N, wlens, rmq, fgeq); if (w_for_m <= W) r = m; else l = m + 1; } int rows = l; int w; vector par; std::tie(w, par) = test(rows, N, wlens, rmq, fgeq); // vv output/formatting section vv vector colpos, colend, widths; int cur = N-1; while (cur >= 0) { colpos.push_back(par[cur]+1); colend.push_back(cur); widths.push_back(rmq.query(par[cur]+1, cur+1)); cur = par[cur]; } reverse(colpos.begin(), colpos.end()); reverse(colend.begin(), colend.end()); reverse(widths.begin(), widths.end()); cout << rows << ' ' << colpos.size() << endl; for (size_t i = 0; i < widths.size(); ++i) cout << widths[i] << " \n"[i+1 == widths.size()]; for (int row = 0; row < rows; ++row) { for (size_t c = 0; c < colpos.size(); ++c) { int spaces = widths[c]; if (colpos[c] <= colend[c]) cout << S[colpos[c]], spaces -= (int)S[colpos[c]].size(); if (spaces) cout << string(spaces, ' '); ++colpos[c]; cout << " \n"[c+1 == colpos.size()]; } } return 0; }