Ta có: \(3 = 1+1+1 = 1+2\), tức có 3 cách tạo thành tổng bằng 3.
\(\Rightarrow\) thứ tự ưu tiên tạo tập số: \(\{3\}, \{1, 2\}, \{1,1,1\}\).
// nhập a, b, c
int tap3 = c;
int tap12 = min(a, b); a -= tap12, b -= tap12;
int tap111 = a / 3;
int answer = tap3+tap12+tap111;
Với một số \(X\) bất kỳ:
\(\Rightarrow\) để tìm trạng thái liền trước của \(X\):
Vì thế, thay vì tìm cách tạo \(B\) từ \(A\), ta sẽ truy ngược từ \(B\) về \(A\).
bool check(int a, int b) {
while (b > 0) {
if (a == b) return true;
if (b % 2 == 0) b /= 2;
else {
if (b % 10 == 1) b /= 10;
else break; // dừng vòng lặp
}
}
// xuống đây tức là không có a
return false;
}
Hướng nghĩ:
\(\Rightarrow\) các con boss sẽ được tiêu diệt theo thứ tự \(p_i\) tăng dần.
\(\Rightarrow\) sắp xếp các boss theo thứ tự \(p\) (nếu cùng \(p\) thì \(g\) như nào không quan trọng, vì cùng \(p\) sẽ cùng bị tiêu diệt một lần, chứ không có chuyện hai boss cùng \(p\) mà một con bị diệt một con thì không)
struct Boss{int p, g;};
bool operator < (Boss a, Boss b); // sắp xếp theo Boss::p
int n; long long S;
Boss bosses[MAX];
main() {
// nhập Boss
sort(bosses + 1, bosses + 1 + n);
int count = 0;
for (i = 1 -> n) {
if (bosses[i].p > S) break;
S += bosses[i].g; count++;
}
cout << count;
}
Lưu ý: Không nhất thiết phải giữ lại toàn bộ các màu (các giá trị khác nhau) có trong mảng, tức được phép mất đi một(vài) màu.
Ở đây giả sử ta bắt buộc phải xóa ít nhất một đoạn. Nếu các phần tử khác nhau đôi một, lập tức in ra \(0\) rồi kết thúc chuogw trình.
Để kiểm tra điều kiện 2, ta sẽ tạo một mảng đếm phân phối dpp[]
số lần xuất hiện của các màu, và một biến cnt
đếm số màu xuất hiện nhiều hơn một lần.
dpp[màu]++
, nếu dpp[màu] == 2
thì cnt++
.
int dpp[MAX_N], cnt = 0;
int answer = 0;
for (l = 1 -> n) for (r = l -> n) {
for (i = 1 -> l-1) {
++dpp[a[i]]; if (dpp[a[i]] == 2) cnt++;
// if (++dpp[a[i]] == 2) cnt++;
// cnt += (++dpp[a[i]] == 2);
}
for (i = r+1 -> n) cnt += (++dpp[a[i]] == 2); // lưu ý: ++a != a++
if (cnt == 0) // không có vi phạm
answer = max(answer, r-l+1);
// sau đó nhớ xóa hết dữ liệu trong mảng dpp và biến cnt.
}
cout << answer;
Độ phức tạp: \(\mathcal{O(n^3)}\); gồm cho \(\mathcal{O(n^2)}\) cho việc chọn cặp \((l,r)\) và \(\mathcal{O}(n)\) cho việc đánh dấu màu.
Khi duyệt các cặp \((l, r)\), với \(l\) cố định thì mỗi lần chỉ thay đổi \(r\) đi \(1\) đơn vị \(\Rightarrow\) bỏ bớt một số ra khỏi tập các số còn lại đang xét.
\(\Rightarrow\) thay vì với mỗi cặp \((l, r)\) ta duyệt các số nằm ngoài đoạn, để lợi dụng sự thay đổi nhỏ này:
Ví dụ:
Giả sử \(n=4\)
l | r | các số được xét |
---|---|---|
1 | chưa duyệt | \(A_1,A_2,A_3,A_4\) |
1 | 1 | \(A_2,A_3,A_4\) |
1 | 2 | \(A_3,A_4\) |
1 | 3 | \(A_4\) |
1 | 4 | |
2 | chưa duyệt | \(A_1,A_2,A_3,A_4\) |
2 | 2 | \(A_1,A_3,A_4\) |
2 | 3 | \(A_1,A_4\) |
2 | 4 | \(A_1\) |
3 | chưa duyệt | \(A_1,A_2,A_3,A_4\) |
3 | 3 | \(A_1,A_2,A_4\) |
3 | 4 | \(A_1,A_2\) |
4 | chưa duyệt | \(A_1,A_2,A_3,A_4\) |
4 | 4 | \(A_1,A_2,A_3\) |
Vì thế, ta cần hỗ trợ thêm phép xóa phần tử:
--dpp[màu]
, nếu dpp[mau] == 1
thì cnt--
for (l = 1 -> n) {
for (i = 1 -> n) cnt += (++dpp[a[i]] == 2)
for (r = l -> n) {
cnt -= (--dpp[a[i]] == 1);
if (cnt == 0) answer = max(answer, r-l+1);
}
}
Độ phức tạp: \(\mathcal{O}(n^2)\)
Ta có thể cải tiến thuật toán bằng cách dừng \(r\) ngay khi cnt == 0
, khi đó \(r\) là vị trí cuối đoạn tối thiểu để phần ngoài có các phần tử độc nhất, từ đó dựng lên các đoạn ngắn nhất. (*)
Nhận thấy: Với một đoạn \((l, r)\) bất kỳ:
Từ (*), khi ta thử tìm \(r\) nhỏ nhất thỏa mãn điều kiện của từng giá trị \(l\), ta thấy khi \(l\) tăng thì \(r\) cũng tăng
Vì khi \(l\) tăng thì bên ngoài thêm một phần tử:
Vì thế, cũng như lúc nãy, ta dựng dpp[]
và cnt
cho toàn mảng \(A\)
cnt==0
A_1
vào dpp
và cập nhật cnt
. Nếu cnt!=0
thì ta tăng \(r\) (loại bỏ \(A_r\) rồi tăng \(r\) lên 1đv) cho tới khi cnt==0
for (i = 1 -> n) cnt += (++dpp[a[i]] == 2)
r = 0;
for (l = 1 -> n) {
while (cnt > 0) {
r++; if (r > n) break;
cnt -= (--dpp[a[r]] == 1);
}
if (r > n) break;
answer = max(answer, r-l+1);
cnt += (++dpp[a[l]] == 2);
}