fork download
  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. using ll = long long;
  4. const ll INF = (ll)1e18;
  5.  
  6. // Mô tả bài toán (tóm tắt):
  7. // - Cho mảng p[1..n] (giá trị dương) và giới hạn tổng k trên mỗi đoạn liên tiếp.
  8. // - L[r] = chỉ số lớn nhất j < r sao cho tổng p[j+1..r] <= k (tức là đoạn (j+1..r) hợp lệ).
  9. // - Ta cần DP: S[i] = chi phí nhỏ nhất để phân chia prefix [1..i],
  10. // với chi phí của mỗi đoạn = maximum phần tử trong đoạn.
  11. // - Chuyển tiếp: S[i] = min_{j in [L[i], i-1]} ( S[j] + max(p[j+1..i]) ).
  12. // Mục tiêu: tính S[n]. Nếu có p[i] > k với bất kỳ i thì không có nghiệm.
  13.  
  14. // Thuật toán trong file này:
  15. // - Sử dụng chiến lược "Divide and Conquer on indices" để tính DP nhanh hơn so với O(n^2).
  16. // - Ý tưởng tổng quát: chia khoảng [l..r] theo mid; giải cho nửa trái rồi sử dụng thông tin
  17. // từ nửa trái để cập nhật các chuyển tiếp cho nửa phải (các j nằm trong [l..mid], i trong [mid+1..r]).
  18. // - Khi khoảng nhỏ (<= 32) dùng brute-force O(len^2) để tránh overhead.
  19. // - Trong nhánh lớn hơn, ta chuẩn bị các mảng phụ (h, q, k) để truy vấn max và tối ưu
  20. // việc cập nhật S[i] cho nhiều i cùng lúc; đồng thời gom các đoạn cần cập nhật vào
  21. // một vector tpls rồi xử lý bằng kỹ thuật "sliding window + min prefix" để giảm chi phí.
  22. // - Độ phức tạp thực nghiệm: gần O(n log n) hoặc O(n log^2 n) tùy data, nhưng rất nhanh
  23. // với n lớn nhờ cắt giảm tìm kiếm và xử lý hàng loạt (batch).
  24.  
  25. struct Tp3 {
  26. int u; // vị trí i ở nửa phải (đích)
  27. int v; // biên trái của khoảng cần xem (>= l)
  28. int w; // biên phải của khoảng cần xem (<= mid)
  29. };
  30.  
  31. // Hàm Dnc: tính và cập nhật S trên đoạn [l..r]
  32. // p: mảng giá trị (1-based)
  33. // S: mảng DP (S[0]=0 ban đầu, S[i] khởi INF)
  34. // L: ràng buộc do tổng phải <= k (L[i] là chỉ số j lớn nhất sao cho j < i và đoạn j+1..i hợp lệ -> trong code là lbound)
  35. void Dnc(int l, int r, const vector<ll> &p, vector<ll> &S, const vector<int> &L) {
  36. if (l >= r) return;
  37. // Ngưỡng nhỏ để dùng brute-force (cố định 32 như bản gốc)
  38. if (r - l <= 32) {
  39. for (int i = l + 1; i <= r; ++i) {
  40. ll curMax = p[i];
  41. for (int j = i - 1; j >= max(l, L[i]); --j) {
  42. // chuyển tiếp S[i] <- S[j] + max(p[j+1..i])
  43. S[i] = min(S[i], S[j] + curMax);
  44. curMax = max(curMax, p[j]);
  45. }
  46. }
  47. return;
  48. }
  49.  
  50. int mid = (l + r) >> 1;
  51. // Giải đệ quy nửa trái trước để S[j] cho j in [l..mid] đã đúng
  52. Dnc(l, mid, p, S, L);
  53.  
  54. // Ta sẽ xử lý các chuyển tiếp j in [l..mid] -> i in [mid+1..r]
  55. // Tạo các mảng tạm dựa trên kích thước đoạn
  56. int len = r - l + 1;
  57. vector<ll> h(len + 5, 0); // h[t] ~ suffix max từ mid về trái (dạng: h[mid-j]) trong code gốc
  58. vector<ll> q(len + 5, INF); // q[t] ~ prefix min S[j] sao cho ... (dùng trong tính nhanh)
  59. vector<ll> kk(len + 5, INF); // kk[idx] tương đương k[j-l] trong code gốc
  60.  
  61. // Tập các 'tpl' (u,v,w) để xử lý nhóm tối ưu sau khi thu thập
  62. vector<Tp3> tpls;
  63.  
  64. // Chuẩn bị q[]: q[0] = S[mid], q[1..] lần lượt = min(S[j], q[prev]) đi ra trái
  65. q[0] = S[mid];
  66. for (int j = mid - 1, t = 1; j >= l; --j, ++t) {
  67. // t = mid - j
  68. h[t] = max(h[t - 1], p[j + 1]); // h[t] lưu max của một dạng cố định theo chỉ số trong logic gốc
  69. q[t] = min(S[j], q[t - 1]);
  70. }
  71.  
  72. // Chuẩn bị kk: kk[idx] = h[mid - j] + S[j] tương tự code gốc
  73. // Ở đây ta đi qua j từ l..mid để viết vào kk[j-l]
  74. for (int j = l; j <= mid; ++j) {
  75. int idx = j - l; // vị trí tương ứng
  76. // mid - j tương đương t ở trên; nếu mid==j thì mid-j=0
  77. int t = mid - j;
  78. kk[idx] = h[t] + S[j];
  79. }
  80.  
  81. // Duyệt i ở nửa phải và cập nhật S[i] bằng hai cách:
  82. // 1) Dùng maxv1 = max(p[mid+1..i]) và q[...] để cập nhật nhanh: S[i] = min(S[i], maxv1 + q[...])
  83. // 2) Nếu cần xét nhiều j liên tiếp trong một khoảng [a..b] sẽ push vào tpls để xử lý sau bằng min over kk[].
  84. int jj = mid;
  85. ll maxv1 = -INF;
  86. for (int i = mid + 1; i <= r; ++i) {
  87. maxv1 = max(maxv1, p[i]);
  88. // Di chuyển jj trái sao cho maxv1 <= h[mid - jj]
  89. // (ý nghĩa: tìm vị trí jj lớn nhất thỏa điều kiện)
  90. while (jj >= l && maxv1 > h[mid - jj]) --jj;
  91.  
  92. // Cập nhật nhanh sử dụng q: j phải >= (jj+1) và j <= mid
  93. int leftBound = max(jj + 1, L[i]);
  94. if (leftBound <= mid) {
  95. // q[mid - leftBound] tương ứng giá trị min S[j] trong phạm vi cần
  96. int idx = mid - leftBound;
  97. S[i] = min(S[i], maxv1 + q[idx]);
  98. }
  99.  
  100. // Nếu vẫn tồn tại phần j trong [L[i], jj] thì chúng ta cần tính:
  101. // min_{j in [max(L[i], l) .. jj]} kk[j-l] và dùng để cập nhật S[i]
  102. if (max(l, L[i]) <= jj) {
  103. tpls.push_back({i, max(l, L[i]), jj});
  104. }
  105. }
  106.  
  107. // Nếu có các tpl cần xử lý, ta thực hiện 1 quy trình lấy min động trên kk[]
  108. if (!tpls.empty()) {
  109. // Đảo ngược để sắp xếp theo v giảm (giống code gốc xử lý theo thứ tự thuận lợi)
  110. reverse(tpls.begin(), tpls.end());
  111.  
  112. // cl..cr là cửa sổ hiện hành trên chỉ số j (biểu diễn bằng j-l), cw là min kk trong cửa sổ
  113. int cl = tpls[0].v; // giá trị j ban đầu (chú ý là chỉ số thực j, không là offset)
  114. int cr = tpls[0].v - 1; // khởi cửa sổ rỗng
  115. ll cw = INF;
  116.  
  117. // Hàm tiện: truy xuất kk[offset]
  118. auto getkk = [&](int j)->ll { return kk[j - l]; };
  119.  
  120. for (auto &t : tpls) {
  121. // mở rộng cửa sổ để bao phủ [t.v .. t.w]
  122. while (cl > t.v) {
  123. --cl;
  124. cw = min(cw, getkk(cl));
  125. }
  126. while (cr < t.w) {
  127. ++cr;
  128. cw = min(cw, getkk(cr));
  129. }
  130. // cập nhật S[t.u] với cw (min kk trên khoảng cần)
  131. S[t.u] = min(S[t.u], cw);
  132. }
  133. // giải phóng
  134. tpls.clear();
  135. }
  136.  
  137. // Đệ quy nửa phải
  138. Dnc(mid + 1, r, p, S, L);
  139. }
  140.  
  141. void solve_one_case() {
  142. int n;
  143. ll k;
  144. if (!(cin >> n >> k)) return;
  145. vector<ll> p(n + 1);
  146. for (int i = 1; i <= n; ++i) cin >> p[i];
  147.  
  148. // Nếu có phần tử > k thì không thể tạo đoạn chứa phần tử đó (mọi đoạn có max >= phần tử đó > k
  149. // và tổng đoạn phải <= k -> vô nghiệm theo yêu cầu ban đầu), trả -1.
  150. for (int i = 1; i <= n; ++i) {
  151. if (p[i] > k) {
  152. cout << -1 << '\n';
  153. return;
  154. }
  155. }
  156.  
  157. // Tính mảng L: cho mỗi r, tìm l0 sao cho tổng p[l0..r] <= k và nếu thêm p[l0-1] thì vượt
  158. vector<int> L(n + 1);
  159. ll sum = 0;
  160. int lptr = 1;
  161. for (int r = 1; r <= n; ++r) {
  162. sum += p[r];
  163. while (sum > k) {
  164. sum -= p[lptr++];
  165. }
  166. // sau khi điều chỉnh, đoạn [lptr..r] hợp lệ, nghĩa là j = lptr-1 là chỉ số lớn nhất sao cho j < r và j thỏa
  167. L[r] = lptr - 1; // j chạy từ L[r] đến r-1
  168. }
  169.  
  170. // DP S
  171. vector<ll> S(n + 1, INF);
  172. S[0] = 0;
  173.  
  174. // Chạy thuật toán D&C để tính S[n]
  175. Dnc(0, n, p, S, L);
  176.  
  177. cout << S[n] << '\n';
  178. }
  179.  
  180. int main() {
  181. ios::sync_with_stdio(false);
  182. cin.tie(nullptr);
  183.  
  184. int T = 1;
  185. if (!(cin >> T)) return 0;
  186. while (T--) solve_one_case();
  187. return 0;
  188. }
  189.  
Success #stdin #stdout 0.01s 5288KB
stdin
Standard input is empty
stdout
Standard output is empty