# **Vấn đề** [CSES - Static Range Sum Queries](https://cses.fi/problemset/task/1646) Cho mảng $A$ gồm $n$ số được đánh số từ $1 \rightarrow n$. Trả lời $q$ truy vấn $l, r$ hỏi tổng trong đoạn $[l, r]$ là bao nhiêu. ## Input - Dòng đầu ghi $n, q$ là số lượng số trong mảng $A$ và số lượng truy vấn $(n, q \le 2.10^5)$ - <p>Dòng tiếp theo ghi $n$ số $a_1, a_2, ..., a_n$.</p> - $q$ dòng sau, mỗi dòng ghi 2 số $l,r$ ## Output - Gồm $q$ dòng, mỗi dòng ghi kết quả của truy vấn $l_i, r_i$ ## Ví dụ ### Input ``` 6 3 2 3 4 5 1 8 1 4 3 6 2 5 ``` ### Output ``` 14 18 13 ``` # Ý tưởng ngây thơ - Theo 1 ý nghĩ thông thường, đề bảo gì làm đó. Nghĩa là với mỗi truy vấn $l_i, r_i$ chúng ta sẽ duyệt $l \rightarrow r$ và cộng $a_i$ lại rồi in ra kết quả - Cài đặt như sau ```cpp= while (q--) { int l, r; cin >> l >> r; long long ans = 0; for (int i = l; i <= r; i++) { ans += a[i]; } cout << ans << endl; } ``` - Độ phức tạp cho ý tưởng ngây thơ trên sẽ là $O(n.q)$ - Thế nhưng với giới hạn trên thì ý tưởng trên có vẻ là bất khả thi trong giới hạn $1s$. # Nhận xét - Với giới hạn lớn như trên chúng ta có thể xây dựng 1 thuật toán chạy $O(n + q)$ trở xuống. - Dễ nhận thấy rằng việc tính đi tính lại vòng for $l$ $\rightarrow$ $r$ là không cần thiết. Vậy câu hỏi đặt ra rằng liệu chúng ta có thể xây dựng trước toàn bộ tổng $l, r$ trên hay không? - Gọi $S(i)$ là tổng các số trong đoạn từ $1 \rightarrow i$. Vậy ta muốn tính tổng trong đoạn từ $l \rightarrow r$, ta có công thức sau: $S(l...r) = S(r) - S(l - 1)$ - Để dễ hình dung công thức này ta có hình vẽ như sau: ![](https://hackmd.io/_uploads/rkuem61hn.png) - Vậy để xây dựng tổng từ $1 \rightarrow i$ ta có công thức như sau: $S[i] = S[i - 1] + a[i]$ - Với $S[i-1]$ là tổng từ $1 \rightarrow i - 1$ đằng trước và khi thêm $a[i]$ vào cuối. # Ý tưởng tối ưu - Với toàn bộ các nhận xét trên, chúng ta có thể viết vô cùng dễ dàng với code như sau: ```cpp= //preprocess for (int i = 1; i <= n; i++) { S[i] = S[i-1] + a[i]; } while (q--) { int l, r; cin >> l >> r; cout << S[r] - S[l - 1] << endl; } ``` - Tiền xử lí mảng $S$: $O(n)$ - Trả lời $q$ truy vấn: $O(q)$ - Tổng độ phức tạp: $O(n + q)$ # Bài tập áp dụng [CSES - Maximum Subarray Sum](https://cses.fi/problemset/task/1643) ## Tóm tắt Cho mảng $A$ gồm $n$ phần tử. Tìm mảng con liên tiếp khác rỗng có tổng lớn nhất. $(1 \le n \le 2.10^5, |a_i| \le 10^9)$ ### Input ``` 8 -1 3 -2 5 3 -5 2 2 ``` ### Output ``` 9 ``` ### Giải thích Chọn đoạn $[3, -2, 5, 3]$ ## Lời giải - Bài này là 1 bài kinh điển áp dụng [thuật toán Kadane](https://www.geeksforgeeks.org/largest-sum-contiguous-subarray/). Thế nhưng trong giới hạn bài viết chúng ta sẽ dùng prefix sum để giải bài này. - Như bên trên, chúng ta sẽ xây dựng $S[i]$ là tổng các số trong đoạn từ $1 \rightarrow i$ - Vậy theo đề yêu cầu chúng ta sẽ cần tìm 1 đoạn $l, r$ trong mảng $A$ sao cho tổng là lớn nhất. Dễ nhận thấy bằng phương pháp tham lam, giả sử $r$ cố định, chúng ta sẽ muốn tìm $S(l - 1)$ thấp nhất. Bởi vì khi $S(l - 1)$ thấp nhất, thì $S(r) - S(l - 1)$ sẽ là lớn nhất. Chúng ta có thể viết bài toán lại theo công thức sau: $ans_r = max(S(r) - S(l - 1))$ $ans_r = S(r) + max(-S(l - 1))$ $ans_r = S(r) - min(S(l - 1))$ - Vậy chúng ta có thể giải bài này bằng $O(n)$ như sau: ```cpp= for (int i = 1; i <= n; i++) S[i] = S[i-1] + a[i]; long long m = INF; // INF = 1e18 long long ans = -INF; for (int r = 0; r <= n; r++) { ans = max(ans, S[r] - m); m = min(m, S[r]); } cout << ans << endl; ``` # Bài tập thêm - [CSES - Subarray Sums II](https://cses.fi/problemset/task/1661) - [CSES - Subarray Divisibility](https://cses.fi/problemset/task/1662) - [Atcoder - GCD on Blackboard](https://atcoder.jp/contests/abc125/tasks/abc125_c) - [VNOJ - NKSEQ](https://oj.vnoi.info/problem/nkseq) - [CF - Good Subarrays](https://codeforces.com/contest/1398/problem/C)