Chương 0·Bài 02

Prefix Sum

Cho một dãy nn số và qq câu hỏi, mỗi câu hỏi tổng các phần tử từ vị trí ll đến vị trí rr. Cộng lại từng đoạn mất tới nn phép cộng cho một câu, cả bài là n⋅qn \cdot q; với n=q=2⋅105n = q = 2 \cdot 10^5 con số này là 4⋅10104 \cdot 10^{10}, quá xa giới hạn một giây. Tổng tiền tố (prefix sum) trả lời mỗi câu bằng đúng một phép trừ, sau một lượt chuẩn bị O(n)O(n). Ý tưởng chỉ một dòng, nhưng nó là nền của mảng hiệu, tổng hai chiều, đếm đoạn con, và về sau là cây Fenwick, cây phân đoạn.

Tổng của một đoạn bằng hiệu hai tiền tố

Đánh số dãy từ 1: a1,a2,…,ana_1, a_2, \dots, a_n. Định nghĩa

pre0=0,prei=a1+a2+⋯+ai=prei−1+ai.\mathrm{pre}_0 = 0, \qquad \mathrm{pre}_i = a_1 + a_2 + \dots + a_i = \mathrm{pre}_{i-1} + a_i .

Mỗi prei\mathrm{pre}_i là tổng của tiền tố độ dài ii. Tổng đoạn [l,r][l, r] là tiền tố dài rr bỏ đi tiền tố dài l−1l - 1:

al+al+1+⋯+ar=prer−prel−1.a_l + a_{l+1} + \dots + a_r = \mathrm{pre}_r - \mathrm{pre}_{l-1}.

Đẳng thức đúng vì prer\mathrm{pre}_r gồm a1,…,al−1a_1, \dots, a_{l-1} và al,…,ara_l, \dots, a_r; phép trừ xóa đúng phần đầu. Ô pre0=0\mathrm{pre}_0 = 0 không thừa: nhờ nó, đoạn bắt đầu từ l=1l = 1 vẫn dùng chung công thức prer−pre0\mathrm{pre}_r - \mathrm{pre}_0.

Với a=(3,1,4,1,5,9,2,6)a = (3, 1, 4, 1, 5, 9, 2, 6), mảng pre\mathrm{pre} là (0,3,4,8,9,14,23,25,31)(0, 3, 4, 8, 9, 14, 23, 25, 31). Tổng đoạn [3,6][3, 6] là 4+1+5+9=194 + 1 + 5 + 9 = 19, đúng bằng pre6−pre2=23−4\mathrm{pre}_6 - \mathrm{pre}_2 = 23 - 4.

Kéo l, r: tổng đoạn luôn bằng hiệu hai ô của pre.

Hai chỗ hay sai nằm ngay trong công thức ngắn này. Thứ nhất là kiểu dữ liệu: mỗi aia_i vừa int, nhưng tổng của 2⋅1052 \cdot 10^5 số cỡ 10910^9 lên tới 2⋅10142 \cdot 10^{14}, nên pre\mathrm{pre} phải là long long. Thứ hai là chỉ số: đọc dãy vào a[0..n-1] mà vẫn viết pre[r] - pre[l - 1] thì mọi câu trả lời lệch một vị trí. Cách an toàn nhất là để pre\mathrm{pre} có n+1n + 1 ô và luôn hiểu prei\mathrm{pre}_i là tổng ii phần tử đầu.

Hỏi nhanh

Dãy được đọc vào a[0], a[1], ..., a[n-1] (đánh số từ 0), và ta dựng pre[i] = a[0] + ... + a[i-1] với pre[0] = 0. Câu hỏi cũng dùng chỉ số từ 0: tổng a[l] + ... + a[r]. Biểu thức nào đúng?

Ví dụTổng Trên Đoạn Cố Định

Đề. Dãy nn số nguyên dương xi≤109x_i \le 10^9 không đổi, qq truy vấn [l,r][l, r], in tổng đoạn. n,q≤2⋅105n, q \le 2 \cdot 10^5.

Lời giải. Đúng bài toán của mục này. Dựng pre\mathrm{pre} khi đọc dãy, mỗi truy vấn in prer−prel−1\mathrm{pre}_r - \mathrm{pre}_{l-1}. Cả bài O(n+q)O(n + q), tổng lớn nhất cỡ 2⋅10142 \cdot 10^{14} nên dùng long long.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5;
ll n, q, a[N], pre[N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> q;
    for (ll i = 1; i <= n; i++) {
        cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
    }
    while (q--) {
        ll l, r;
        cin >> l >> r;
        cout << pre[r] - pre[l - 1] << '\n';
    }
}
7 4
4 2 7 1 3 5 6
1 1
2 5
4 7
1 7

Không chỉ là phép cộng

Công thức prer−prel−1\mathrm{pre}_r - \mathrm{pre}_{l-1} chỉ dùng hai tính chất của phép cộng: kết hợp được, và có phép ngược để gỡ phần đầu ra. Phép toán nào có hai tính chất đó cũng dựng tiền tố được.

  • XOR. Phép XOR, viết x⊕yx \oplus y và trong C++ là x ^ y, so hai số theo từng chữ số nhị phân: hai chữ số khác nhau cho 1, giống nhau cho 0. Chẳng hạn 5⊕3=1012⊕0112=1102=65 \oplus 3 = 101_2 \oplus 011_2 = 110_2 = 6. Đặt pxi=a1⊕⋯⊕ai\mathrm{px}_i = a_1 \oplus \dots \oplus a_i. Vì x⊕x=0x \oplus x = 0, các phần tử trước ll xuất hiện hai lần trong pxr⊕pxl−1\mathrm{px}_r \oplus \mathrm{px}_{l-1} và tự triệt tiêu, nên XOR đoạn [l,r][l, r] là pxr⊕pxl−1\mathrm{px}_r \oplus \mathrm{px}_{l-1}.
  • Đếm. Muốn biết đoạn [l,r][l, r] có bao nhiêu phần tử thỏa một tính chất (số chẵn, ký tự a, ô có cây), dựng tiền tố của dãy đánh dấu bib_i, bằng 1 nếu phần tử thứ ii thỏa và 0 nếu không. Với chuỗi chữ thường, 26 mảng tiền tố cho tần suất từng chữ trong mọi đoạn.

Phép min và max thì không gỡ được: biết số nhỏ nhất của a1..ara_1..a_r và của a1..al−1a_1..a_{l-1} không cho biết số nhỏ nhất của al..ara_l..a_r. Với các phép này cần công cụ khác: bảng thưa (bài 22) khi dãy không đổi, cây phân đoạn (bài 17) khi dãy bị sửa. Tiền tố của min vẫn có ích khi đoạn luôn bắt đầu từ 1, ví dụ “giá trị nhỏ nhất trong ii phần tử đầu”.

Hỏi nhanh

Bài nào không giải được bằng một mảng tiền tố dựng sẵn rồi trả lời mỗi truy vấn trong O(1)O(1)? (Dãy không thay đổi giữa các truy vấn.)

Ví dụĐếm Cặp AC Trên Đoạn

Đề. Chuỗi SS dài n≤105n \le 10^5 gồm A, C, G, T. Mỗi truy vấn [l,r][l, r] hỏi có bao nhiêu chỉ số ii với l≤i<rl \le i < r mà SiSi+1S_i S_{i+1} là AC.

Lời giải. Một cặp AC chiếm hai ô nên phải chọn gắn nó với ô nào. Gắn với ô bên phải: đặt bi=1b_i = 1 nếu Si−1SiS_{i-1} S_i là AC. Cặp nằm trọn trong [l,r][l, r] khi ô phải của nó thuộc [l+1,r][l + 1, r], vậy đáp án là prer−prel\mathrm{pre}_r - \mathrm{pre}_l, không phải prer−prel−1\mathrm{pre}_r - \mathrm{pre}_{l-1}. Viết nhầm thành l−1l - 1 sẽ đếm cả cặp vắt qua biên trái, có ô A ở vị trí l−1l - 1.

Trên ví dụ AACACGACCT, các cặp có ô phải ở 3, 5, 8. Truy vấn [2,5][2, 5] lấy hai ô 3 và 5; truy vấn [8,10][8, 10] không lấy ô 8, vì A ở vị trí 7 nằm ngoài đoạn.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1e5 + 5;
ll n, q, pre[N];
string s;

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> q >> s;
    s = " " + s;
    for (ll i = 1; i <= n; i++) pre[i] = pre[i - 1] + (i > 1 && s[i - 1] == 'A' && s[i] == 'C');
    while (q--) {
        ll l, r;
        cin >> l >> r;
        cout << pre[r] - pre[l] << '\n';
    }
}
10 5
AACACGACCT
1 10
2 5
3 7
5 8
8 10

Đếm đoạn con: chuyển về cặp tiền tố

Rất nhiều bài hỏi “có bao nhiêu đoạn con thỏa…”. Có n(n+1)2\frac{n(n+1)}{2} đoạn, duyệt hết là O(n2)O(n^2). Mẹo là viết điều kiện của đoạn [l,r][l, r] thành điều kiện trên hai số prel−1\mathrm{pre}_{l-1} và prer\mathrm{pre}_r, rồi cố định đầu phải rr và đếm xem có bao nhiêu chỉ số j=l−1<rj = l - 1 < r ghép được với nó.

Ví dụ kinh điển: đếm đoạn có tổng bằng kk. Điều kiện prer−prej=k\mathrm{pre}_r - \mathrm{pre}_j = k tương đương prej=prer−k\mathrm{pre}_j = \mathrm{pre}_r - k. Duyệt rr từ trái sang phải, giữ bảng đếm cnt các giá trị pre0,…,prer−1\mathrm{pre}_0, \dots, \mathrm{pre}_{r-1} đã gặp; mỗi đầu phải rr góp cnt[pre[r] - k] đoạn. Bảng đếm có sẵn giá trị pre0=0\mathrm{pre}_0 = 0 với số lần 1, chính là tiền tố rỗng ứng với các đoạn bắt đầu từ l=1l = 1.

C++
map<ll, ll> cnt;
cnt[0] = 1;
ll cur = 0, ans = 0;
for (ll i = 1; i <= n; i++) {
    cur += a[i];
    ans += cnt[cur - k];
    cnt[cur]++;
}

Thứ tự hai dòng cuối trong vòng lặp quan trọng: phải cộng cnt[cur - k] trước khi thêm cur của chính đầu phải này, nếu không với k=0k = 0 ta đếm cả đoạn rỗng j=rj = r. map (bài 09) là bảng đếm theo giá trị bất kỳ, kể cả số âm hay số rất lớn, đổi lại mỗi bước chậm hơn mảng; khi tiền tố chỉ nhận ít giá trị (ví dụ phần dư khi chia cho mm) thì dùng mảng đếm.

Cùng khuôn đó giải được nhiều điều kiện khác. Tổng đoạn chia hết cho mm khi prer\mathrm{pre}_r và prej\mathrm{pre}_j có cùng số dư khi chia cho mm: đếm cặp cùng phần dư. Tổng đoạn bằng độ dài đoạn khi prer−r=prej−j\mathrm{pre}_r - r = \mathrm{pre}_j - j: đếm cặp cùng giá trị prei−i\mathrm{pre}_i - i. Tổng đoạn lớn nhất: với mỗi rr lấy prer\mathrm{pre}_r trừ tiền tố nhỏ nhất đứng trước nó, rồi lấy lớn nhất qua mọi rr; thay bảng đếm bằng một biến giữ tiền tố nhỏ nhất. Bước khó nhất luôn là biến đổi để một vế chỉ chứa rr, vế kia chỉ chứa jj.

Dữ liệu của ví dụ dưới: mỗi cung nối hai tiền tố cùng dư là một đoạn chia hết cho 6.

Hỏi nhanh

Đếm các đoạn [l,r][l, r] có tổng bằng độ dài r−l+1r - l + 1. Đặt ci=prei−ic_i = \mathrm{pre}_i - i. Điều kiện của đoạn tương đương với điều gì?

Ví dụĐếm Đoạn Có Tổng Chia Hết

Đề. Dãy N≤2⋅105N \le 2 \cdot 10^5 số nguyên, ∣Ai∣≤109|A_i| \le 10^9, có thể âm. Đếm số đoạn con có tổng chia hết cho NN.

Lời giải. Đoạn [l,r][l, r] thỏa khi và chỉ khi prer−prel−1\mathrm{pre}_r - \mathrm{pre}_{l-1} chia hết cho NN, tức hai tiền tố có cùng số dư khi chia cho NN, nên mỗi cặp tiền tố cùng phần dư ứng với đúng một đoạn. Duyệt rr, giữ cnt[d] là số tiền tố đứng trước có phần dư dd, cộng cnt của phần dư hiện tại vào đáp án rồi mới tăng nó. Phần dư chỉ có NN giá trị nên dùng mảng, cả bài O(N)O(N).

Chỗ dễ sai là số âm: trong C++, -7 % 6 bằng -1 chứ không phải 5, nên phải đưa phần dư về khoảng từ 00 tới N−1N - 1 bằng ((x % n) + n) % n, nếu không hai tiền tố cùng số dư rơi vào hai ô khác nhau. Đáp án có thể tới N(N+1)2≈2⋅1010\frac{N(N+1)}{2} \approx 2 \cdot 10^{10}, cần long long.

Trên ví dụ, tiền tố là 0,2,−1,3,4,0,60, 2, -1, 3, 4, 0, 6, phần dư theo 6 là 0,2,5,3,4,0,00, 2, 5, 3, 4, 0, 0. Ba tiền tố dư 0 (vị trí 0, 5, 6) cho ba cặp, ứng với các đoạn [1,5][1, 5], [1,6][1, 6], [6,6][6, 6].

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5;
ll n, cnt[N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    cnt[0] = 1;
    ll cur = 0, ans = 0;
    for (ll i = 1; i <= n; i++) {
        ll x;
        cin >> x;
        cur = ((cur + x) % n + n) % n;
        ans += cnt[cur];
        cnt[cur]++;
    }
    cout << ans << '\n';
}
6
2 -3 4 1 -4 6

Mảng hiệu: cộng cả đoạn chỉ bằng hai phép sửa

Bài toán ngược: dãy ban đầu toàn 0, có mm thao tác “cộng xx vào mọi phần tử từ ll đến rr”, cuối cùng in dãy. Làm thẳng tốn O(n)O(n) mỗi thao tác. Mảng hiệu (difference array) diff\mathrm{diff} làm mỗi thao tác trong O(1)O(1):

diffl+=x,diffr+1−=x.\mathrm{diff}_l \mathrel{+}= x, \qquad \mathrm{diff}_{r+1} \mathrel{-}= x .

Sau mọi thao tác, ai=diff1+⋯+diffia_i = \mathrm{diff}_1 + \dots + \mathrm{diff}_i, tức aa là tổng tiền tố của diff\mathrm{diff}. Một thao tác trên [l,r][l, r] làm tổng diff1+⋯+diffi\mathrm{diff}_1 + \dots + \mathrm{diff}_i tăng xx khi i≥li \ge l rồi giảm lại xx khi i≥r+1i \ge r + 1, nên đúng các vị trí l≤i≤rl \le i \le r được cộng xx. Mảng hiệu và tổng tiền tố là hai phép ngược của nhau.

Ba thao tác của chương trình dưới: mỗi thao tác sửa hai ô, một lượt cộng dồn cho lại dãy.
C++
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5;
ll n, m, diff[N];

signed main() {
    cin >> n >> m;
    while (m--) {
        ll l, r, x;
        cin >> l >> r >> x;
        diff[l] += x;
        diff[r + 1] -= x;
    }
    ll cur = 0;
    for (ll i = 1; i <= n; i++) {
        cur += diff[i];
        cout << cur << (i == n ? '\n' : ' ');
    }
}
8 3
2 5 2
4 7 3
1 3 -1

Mảng diff phải có ô n+1n + 1 vì thao tác có r=nr = n sửa tới đó. Kỹ thuật này chỉ dùng được khi mọi thao tác đến trước mọi câu hỏi. Nếu cộng đoạn và hỏi xen kẽ, cần cây Fenwick hoặc cây phân đoạn có cập nhật đoạn.

Mảng hiệu hay xuất hiện ở dạng độ phủ: có nhiều đoạn trên trục số, hỏi mỗi điểm nằm trong bao nhiêu đoạn. Mỗi đoạn cộng 1 lên [l,r][l, r], một lượt cộng dồn cho độ phủ của mọi điểm.

Ví dụĐiểm Đạt Ngưỡng

Đề. Trên trục 1..2⋅1051..2 \cdot 10^5 có NN đoạn. Một điểm nguyên đạt ngưỡng nếu nằm trong ít nhất KK đoạn. QQ truy vấn [a,b][a, b] hỏi đoạn đó chứa bao nhiêu điểm đạt ngưỡng. N,Q≤2⋅105N, Q \le 2 \cdot 10^5.

Lời giải. Hai tầng tiền tố. Tầng một là mảng hiệu cho độ phủ: mỗi đoạn tăng diff[l], giảm diff[r + 1], cộng dồn được số đoạn chứa từng điểm xx. Tầng hai là tiền tố của dãy đánh dấu “điểm xx đạt ngưỡng”, gọi là good. Truy vấn trả lời bằng good[b] - good[a - 1]. Gọi M=2⋅105M = 2 \cdot 10^5 là độ dài trục, cả bài O(N+M+Q)O(N + M + Q); đếm lại từng điểm cho mỗi truy vấn thì tốn tới Q⋅M=4⋅1010Q \cdot M = 4 \cdot 10^{10}.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll M = 2e5;
ll n, k, q, diff[M + 5], good[M + 5];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> k >> q;
    for (ll i = 1; i <= n; i++) {
        ll l, r;
        cin >> l >> r;
        diff[l]++;
        diff[r + 1]--;
    }
    ll cover = 0;
    for (ll x = 1; x <= M; x++) {
        cover += diff[x];
        good[x] = good[x - 1] + (cover >= k);
    }
    while (q--) {
        ll a, b;
        cin >> a >> b;
        cout << good[b] - good[a - 1] << '\n';
    }
}
4 3 5
1 5
3 7
4 9
6 10
1 10
3 6
5 8
1 3
7 10

Tổng tiền tố hai chiều

Trên bảng R×CR \times C, đặt P(i,j)P(i, j) là tổng hình chữ nhật từ ô (1,1)(1, 1) tới ô (i,j)(i, j), với hàng 0 và cột 0 bằng 0. Mỗi ô tính từ ba ô đã có:

P(i,j)=ai,j+P(i−1,j)+P(i,j−1)−P(i−1,j−1).P(i, j) = a_{i,j} + P(i-1, j) + P(i, j-1) - P(i-1, j-1).

Hai hình P(i−1,j)P(i-1, j) và P(i,j−1)P(i, j-1) cùng chứa phần P(i−1,j−1)P(i-1, j-1), nên phần đó bị cộng hai lần và phải trừ đi một lần. Tổng hình chữ nhật từ hàng r1r_1 tới r2r_2, cột c1c_1 tới c2c_2 ghép theo cùng cách nghĩ, gọi là bao hàm loại trừ: lấy hình lớn, trừ hai dải thừa, rồi cộng lại góc bị trừ hai lần:

S=P(r2,c2)−P(r1−1,c2)−P(r2,c1−1)+P(r1−1,c1−1).S = P(r_2, c_2) - P(r_1 - 1, c_2) - P(r_2, c_1 - 1) + P(r_1 - 1, c_1 - 1).
Kéo chuột trên bảng để chọn hình chữ nhật.

Dựng bảng tốn O(RC)O(RC), mỗi câu hỏi O(1)O(1). Mảng hiệu cũng có bản hai chiều: cộng xx lên một hình chữ nhật bằng cách sửa bốn góc, +x+x tại (r1,c1)(r_1, c_1) và (r2+1,c2+1)(r_2 + 1, c_2 + 1), −x-x tại (r1,c2+1)(r_1, c_2 + 1) và (r2+1,c1)(r_2 + 1, c_1), rồi lấy tiền tố hai chiều một lần ở cuối.

Ví dụĐếm Cây Trong Hình Chữ Nhật

Đề. Lưới n×nn \times n (n≤1000n \le 1000), ô * có cây. q≤2⋅105q \le 2 \cdot 10^5 truy vấn, mỗi truy vấn cho góc trên-trái và góc dưới-phải của một hình chữ nhật, hỏi có bao nhiêu cây bên trong.

Lời giải. Coi ô có cây là 1, ô trống là 0, dựng PP theo công thức trên rồi trả lời bằng bốn góc. Dựng O(n2)=106O(n^2) = 10^6, mỗi truy vấn O(1)O(1). Tên biến cho góc tránh y1: trong <cmath> đã có hàm tên đó, khai báo biến toàn cục trùng tên sẽ lỗi biên dịch.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1005;
ll n, q, p[N][N];

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n >> q;
    for (ll i = 1; i <= n; i++) {
        string s;
        cin >> s;
        for (ll j = 1; j <= n; j++) p[i][j] = (s[j - 1] == '*') + p[i - 1][j] + p[i][j - 1] - p[i - 1][j - 1];
    }
    while (q--) {
        ll r1, c1, r2, c2;
        cin >> r1 >> c1 >> r2 >> c2;
        cout << p[r2][c2] - p[r1 - 1][c2] - p[r2][c1 - 1] + p[r1 - 1][c1 - 1] << '\n';
    }
}
5 5
.*..*
**...
..*..
*..**
...*.
1 1 1 5
1 1 5 5
2 2 4 4
3 3 3 3
4 4 5 5

Khi đi thi

  • Thấy nhiều câu hỏi về đoạn trên dãy không đổi là nghĩ ngay tới tiền tố; nếu dãy bị sửa xen giữa các câu hỏi thì chuyển sang cây Fenwick hoặc cây phân đoạn.
  • Viết pre\mathrm{pre} có n+1n + 1 ô với pre0=0\mathrm{pre}_0 = 0, dùng long long, và đưa phần dư âm về khoảng từ 00 tới m−1m - 1 trước khi dùng làm chỉ số.
  • Bài đếm đoạn con: biến đổi điều kiện để một vế chỉ chứa prer\mathrm{pre}_r và rr, vế kia chỉ chứa prej\mathrm{pre}_j và jj, rồi đếm bằng bảng đếm khi duyệt đầu phải.

Tóm tắt

  • prei=prei−1+ai\mathrm{pre}_i = \mathrm{pre}_{i-1} + a_i, pre0=0\mathrm{pre}_0 = 0; tổng đoạn [l,r][l, r] là prer−prel−1\mathrm{pre}_r - \mathrm{pre}_{l-1}, dựng O(n)O(n), hỏi O(1)O(1).
  • Phép toán kết hợp có phép ngược (cộng, XOR, đếm) đều dựng tiền tố được; min, max thì không.
  • Đếm đoạn con thỏa điều kiện: cố định đầu phải, đếm các tiền tố trước đó ghép được, nhớ đưa tiền tố rỗng vào từ đầu.
  • Mảng hiệu là phép ngược của tiền tố: cộng một đoạn bằng hai phép sửa, cộng dồn một lần ở cuối.
  • Bản hai chiều dựng và hỏi bằng bao hàm loại trừ trên bốn góc.

Bài tập

1
Khôi Phục Dãy Ban Đầucanuc80k0200Đi ngược công thức: ai=Si−Si−1a_i = S_i - S_{i-1}.
LQDOJ
2
XOR Trên Đoạncanuc80k0205Tiền tố XOR, thay phép trừ bằng XOR.
LQDOJ
3
Cặp Ký Tự Liền Nhaucanuc80k0204Dựng tiền tố của dãy đánh dấu: ký tự ở ii có giống ký tự ở i+1i + 1 không.
LQDOJ
4
Đoạn Nhẹ Nhấtcanuc80k0202Mọi đoạn dài đúng kk: duyệt đầu trái, lấy tổng bằng tiền tố, giữ vị trí tốt nhất.
LQDOJ
5
Điểm Hội Tụcanuc80k0213Với mỗi vị trí đứng, số người phải quay là một tổng bên trái cộng một tổng bên phải.
LQDOJ
6
Khôi Phục Một Dải Đèncanuc80k0216Mỗi cửa sổ dài KK cần sửa bao nhiêu đèn hỏng: tiền tố của dãy 0/1.
LQDOJ
7
Đoạn Giàu Đỉnhcanuc80k0222Đánh dấu các đỉnh rồi lấy tiền tố; cửa sổ dài KK không tính hai đầu mút.
LQDOJ
8
Đoạn Mã Dài Nhất Chia Hết Cho Bảycanuc80k0229Hai tiền tố cùng phần dư theo 7; đoạn dài nhất ghép với lần xuất hiện đầu tiên của phần dư.
LQDOJ
9
Đoạn Có Tổng Bằng Độ Dàicanuc80k0243Biến đổi điều kiện về cr=cl−1c_r = c_{l-1} với ci=prei−ic_i = \mathrm{pre}_i - i rồi đếm cặp bằng nhau.
LQDOJ
10
Ba Đoạn Cùng Tổngcanuc80k0231Hai điểm cắt có tiền tố bằng S/3S/3 và 2S/32S/3; đếm theo thứ tự để điểm cắt thứ nhất đứng trước.
LQDOJ
11
Các Lệnh Theo Nhómcanuc80k0219Mảng hiệu dùng hai lần: một lần trên các thao tác, một lần trên dãy.
LQDOJ
12
Hoán Vị Tối Ưu Cho Các Đoạncanuc80k0240Mảng hiệu cho biết mỗi vị trí được hỏi bao nhiêu lần; sau đó ghép số lớn với vị trí được hỏi nhiều.
LQDOJ
13
Những Chuyến Nằm Trọn Trong Đoạncanuc80k0227Mỗi chuyến là một điểm trên bảng N×NN \times N; truy vấn là tổng một vùng, dùng tiền tố hai chiều.
LQDOJ
14
Trung Vị Thấp Nhấtcanuc80k0246Chặt nhị phân giá trị trung vị, mỗi lần kiểm bằng tiền tố hai chiều trên bảng 0/1.
LQDOJ

Đọc thêm