Hướng dẫn cho LQDOJ CUP 2022 - Round 6 - SOLDIER
Chép code từ bài hướng dẫn để nộp bài là hành vi có thể dẫn đến khóa tài khoản.
Authors:
Subtask \(1\) (\(20\%\) số điểm): \(n \leq 20\).
Tutorial
Duyệt hết tất cả các đội hình tiêu diệt được quái vật. Với mỗi đội hình đó, duyệt từng quân lính có trong đội hình. Nếu bỏ quân lính đó đi và giữ nguyên các quân lính còn lại, đội hình đó không tiêu diệt quái vật được nữa thì đánh dấu quân lính đó là quan trọng. Với một quân lính \(i\), nếu sau khi duyệt hết tất cả các đội hình thoả mãn mà quân lính đó không được đánh dấu thì quân lính đó không quan trọng.
Độ phức tạp: \(\mathcal{O}(2^n \times n)\).
Solution
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 5005;
int n, k;
int p[MAX_N], state[MAX_N];
bool ans[MAX_N];
void backtrack(int pos, long long sum) {
if (pos == n + 1) {
if (sum >= k) {
for (int i = 1; i <= n; ++i) {
if (state[i] && sum - p[i] < k) {
ans[i] = true;
}
}
}
return;
}
backtrack(pos + 1, sum);
state[pos] = true;
backtrack(pos + 1, sum + p[pos]);
state[pos] = false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
freopen("SOLDIER.inp", "r", stdin);
freopen("SOLDIER.out", "w", stdout);
cin >> n >> k;
for (int i = 1; i <= n; ++i) {
cin >> p[i];
}
backtrack(1, 0);
for (int i = 1; i <= n; ++i) {
cout << ans[i];
}
return 0;
}
Subtask \(2\) (\(40\%\) số điểm): \(n, k \leq 400\).
Tutorial
Với mỗi quân lính \(i\), ta sẽ tìm đội hình có tổng sức mạnh lớn nhất nhỏ hơn \(k\) mà không chứa quân lính thứ \(i\), nếu tổng sức mạnh của đội hình đó cộng với sức mạnh của quân lính thứ \(i\) lớn hơn hoặc bằng \(k\) thì quân lính đó quan trọng. Ngược lại quân lính đó không quan trọng bởi vì nếu quân lính thứ \(i\) quan trọng thì tức là đội hình tìm được chưa phải là đội hình có tổng sức mạnh lớn nhất mà nhỏ hơn \(k\).
Để tìm được đội hình có tổng sức mạnh lớn nhất nhỏ hơn \(k\) mà không chứa quân lính thứ \(i\), ta sử dụng kỹ thuật quy hoạch động cái túi để giải quyết bài toán này (không xét quân lính thứ \(i\)).
Độ phức tạp: \(\mathcal{O}(n^2 \times k)\)
Solution
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 5005;
int n, k;
int a[MAX_N];
bool dp[MAX_N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
freopen("SOLDIER.inp", "r", stdin);
freopen("SOLDIER.out", "w", stdout);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int p = 1; p <= n; p++) {
if (a[p] >= k) {
cout << 1;
} else {
fill(dp, dp + k, false);
dp[0] = true;
for (int i = 1; i <= n; i++)
if (i != p) {
for (int j = k - 1; j >= a[i]; j--) {
dp[j] |= dp[j - a[i]];
}
}
bool ans = false;
for (int i = k - a[p]; i < k; i++) {
ans |= dp[i];
}
cout << ans;
}
}
return 0;
}
Subtask \(3\) (\(40\%\) số điểm): không có ràng buộc gì thêm.
Tutorial
Ở subtask này, ta sẽ thay đổi thuật toán ở subtask 2 một chút. Thay vì với mỗi quân lính thứ \(i\) tìm đội hình có tổng sức mạnh lớn nhất nhỏ hơn \(k\) mà không chứa quân lính thứ \(i\), ta chỉ cần kiểm tra liệu có tồn tại một đội hình (không chứa quân lính thứ \(i\)) nào có tổng sức mạnh nằm trong đoạn \([k-p_i, \ k-1]\). Bởi vì nếu tồn tại một đội hình như thế, đội hình đó nếu không có quân lính thứ \(i\) thì không tiêu diệt được quái vật, nhưng nếu thêm vào thì sẽ tiêu diệt được quái vật.
Giả sử tồn tại một đội hình như thế, ta có thể tách đội hình ra làm hai tập, tập thứ nhất là tập các quân lính có sức mạnh nhỏ hơn \(p_i\), tập thứ hai là tập các quân lính có sức mạnh lớn hơn hoặc bằng \(p_i\). Thay tập thứ hai thành tập các quân lính có sức mạnh lớn hơn hoặc bằng \(p_i\) và tập đó là tập có tổng sức mạnh lớn nhất nhỏ hơn \(k\).
Tổng sức mạnh của đội hình mới \(\geq k - p_i\). Nếu như tổng sức mạnh của đội hình mới đó \(> k\) thì ta có thể loại bớt đi các quân lính thuộc tập thứ nhất. Ta hoàn toàn có thể chứng minh được rằng bằng cách loại đi không hoặc một số quân lính ở tập thứ nhất, ta sẽ tạo ra được một đội hình có tổng sức mạnh nằm trong đoạn \([k-p_i, \ k-1]\).
Từ đó ta có thể nghĩ ra được một ý tưởng đó là với mỗi quân lính thứ \(i\), ta tìm tập các quân lính không chứa nó có sức mạnh \(\geq p_i\) sao cho tổng sức mạnh là lớn nhất nhưng vẫn nhỏ hơn \(k\). Sau đó cộng thêm vào sức mạnh của tất cả các quân lính có sức mạnh \(< p_i\). Nếu như tổng sức mạnh khi ấy \(\geq k-p_i\) thì quân lính thứ \(i\) là quan trọng.
Để làm được điều này, ta có thể sắp xếp các quân lính theo thứ tự chỉ số sức mạnh giảm dần, rồi duyệt từ quân lính có sức mạnh lớn nhất (từ \(1\) đến \(n\)). Sử dụng kỹ thuật quy hoạch động cái túi để tìm ra được đội hình có tổng sức mạnh lớn nhất nhỏ hơn \(k\) khi chỉ xét các quân lính từ \(1\) đến \(i-1\). Lấy giá trị đó cộng cho \(\displaystyle\sum_{j=i+1}^{n}{p_j}\). Nếu tổng đó \(\geq k-p_i\) thì quân lính thứ \(i\) là quan trọng. Ngược lại thì không quan trọng.
Độ phức tạp: \((n \times k)\).
Solution
#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 5005;
int n, k;
int a[MAX_N];
int p[MAX_N], dp[MAX_N], suff[MAX_N];
bool ans[MAX_N];
int maxs;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
freopen("SOLDIER.inp", "r", stdin);
freopen("SOLDIER.out", "w", stdout);
cin >> n >> k;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
p[i] = i;
}
sort(p + 1, p + n + 1, [&](int x, int y) {
return a[x] > a[y];
});
for (int i = n; i >= 1; --i) {
if (a[p[i]] >= k) {
suff[i] = suff[i + 1];
} else {
suff[i] = suff[i + 1] + a[p[i]];
}
}
dp[0] = 1;
maxs = 0;
for (int i = 1; i <= n; ++i) {
if (a[p[i]] >= k) {
ans[p[i]] = 1;
} else {
if (maxs + suff[i + 1] >= k - a[p[i]]) {
ans[p[i]] = 1;
}
for (int j = k; j >= 0; --j) {
if (dp[j]) {
if (j + a[p[i]] < k) {
maxs = max(maxs, j + a[p[i]]);
dp[j + a[p[i]]] = 1;
}
}
}
}
}
for (int i = 1; i <= n; ++i) {
cout << ans[i];
}
return 0;
}
Bonus
Hint
Bitset

Bình luận