# Lời giải thi thử lớp 9 lần 2 - TT Tân Khoa
Lưu ý, các lời giải dưới đây là lời "full" subtask. Có thể đọc lời giải để lấy ý tưởng giải các subtask nhỏ hơn.
# Lần 1
## Gravity
Các viết bi được dồn hết sang phải. Sắp xếp lại độ cao tăng dần của các cột bi, ta sẽ có trạng thái cuối cùng.
::: spoiler C++
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
freopen("BAI1.GRAVITY.INP", "r", stdin);
freopen("BAI1.GRAVITY.OUT", "w", stdout);
int n; cin >> n;
vector<long long> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
sort(a.begin(), a.end());
for (auto x : a) cout << x << ' ';
return 0;
}
```
:::
::: spoiler Python
```python
import sys
sys.stdin = open('BAI1.GRAVITY.INP', 'r')
sys.stdout = open('BAI1.GRAVITY.OUT', 'w')
N = int(input())
A = list(map(int,input().split()))
A.sort()
print(*A)
```
:::
## Stable
Sắp xếp các cặp số theo thứ tự tăng dần theo giá trị `a`. Các cặp số có cùng giá trị `a` giữ nguyên thứ tự (không thay đổi thứ tự theo `b`).
Hàm `sort` trong C++ không "stable", thay vào đó sử dụng hàm `stable_sort`.
::: spoiler C++
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
freopen("BAI2.STABLE.INP", "r", stdin);
freopen("BAI2.STABLE.OUT", "w", stdout);
int n; cin >> n;
vector<pair<long long, long long>> a(n);
for (int i = 0; i < n; i++)
cin >> a[i].first >> a[i].second;
stable_sort(a.begin(), a.end(), [](const auto& lhs, const auto& rhs) {
return lhs.first < rhs.first;
});
for (int i = 0; i < n; i++)
cout << a[i].first << ' ' << a[i].second << '\n';
return 0;
}
```
:::
::: spoiler Python
```python
import sys
sys.stdin = open('BAI2.STABLE.INP', 'r')
sys.stdout = open('BAI2.STABLE.OUT', 'w')
n = int(input())
a = [tuple(map(int, input().split())) for _ in range(n)]
a.sort(key=lambda x: x[0])
for x, y in a: print(x, y)
```
:::
## Nth
Với `x` bất kỳ, có thể tìm ra số lượng các số chia hết cho `a` , `b` hoặc `c` không quá `x` trong $O(1)$ (tham khảo sơ đồ Venn phía dưới). Sử dụng thuật toán tìm kiếm nhị phân để tìm ra số thứ `n` thoã mãn điều kiện chia hết.
<img src="https://hackmd.io/_uploads/rJr7tiTta.png" alt="drawing" width="300"/>
$A$: số lượng chia hết cho `a`.
$B$: số lượng chia hết cho `b`.
$C$: số lượng chia hết cho `c`.
$X$: số lượng chia hết cho `a` và `b`.
$Y$: số lượng chia hết cho `a` và `c`.
$Z$: số lượng chia hết cho `b` và `c`.
$Q$: số lượng chia hết cho `a`, `b` và `c`.
Số lượng các số chia hết `a`, `b` hoặc `c` là $A+B+C-X-Y-Z+Q$.
Lưu ý, C++ có trường hợp tràn số (overflow), tham khảo cách xử lý mã nguồn C++ phía dưới. Python không bị tràn, nhưng bị TLE test lớn
::: spoiler C++
```cpp
#include <bits/stdc++.h>
using namespace std;
long long ab, ac, bc, abc, a, b, c;
long long cnt(long long x) {
long long A = x / a;
long long B = x / b;
long long C = x / c;
long long X = x / ab;
long long Y = x / ac;
long long Z = x / bc;
long long Q = x / abc;
if (abc % a || abc % b || abc % c) Q = 0; // overflow
return A + B + C - X - Y - Z + Q;
}
int main() {
ios_base::sync_with_stdio(false); cin.tie(NULL);
freopen("BAI3.NTH.INP", "r", stdin);
freopen("BAI3.NTH.OUT", "w", stdout);
int q; cin >> q;
while (q--) {
long long n;
cin >> a >> b >> c >> n;
ab = lcm(a, b);
ac = lcm(a, c);
bc = lcm(b, c);
abc = lcm(ab, bc);
long long l = 1, r = min({a, b, c}) * n;
while (l < r) {
long long m = (l + r) / 2;
if (cnt(m) < n)
l = m + 1;
else
r = m;
}
cout << l << '\n';
}
return 0;
}
```
:::
::: spoiler Python
```python=
from math import lcm
import sys
sys.stdin = open('BAI3.NTH.INP', 'r')
sys.stdout = open('BAI3.NTH.OUT', 'w')
def cnt(x):
A = x // a
B = x // b
C = x // c
X = x // ab
Y = x // ac
Z = x // bc
Q = x // abc
return A + B + C - X - Y - Z + Q
q = int(input())
ans = []
for i in range(q):
a, b, c, n = map(int, input().split())
ab = lcm(a, b)
ac = lcm(a, c)
bc = lcm(b, c)
abc = lcm(a, b, c)
l, r = 1, 10 ** 18
while l < r:
m = (l + r) // 2
if cnt(m) < n:
l = m + 1
else:
r = m
ans.append(l)
print(*ans)
```
:::
## Fairgame
Các số mà T.Hòa lấy, hai số bất kỳ trong đó luôn chênh lệch nhau hơn $k$ hoặc bằng nhau. Sắp xếp lại dãy $a$, và dùng công thức QHĐ như sau.
`F[i]` là điểm tối ưu của T.Hòa với dãy số $a_0, a_1, ..., a_i$ và T.Hòa luôn lấy $a_i$.
Với $j < i$ thõa mãn $a[j]=a[i]$ hoặc $a[i]-a[j]>k$
Công thức QHĐ: $F[i] \xleftarrow{max} F[j] + a[i]$.
Lưu ý, điểm T.Hòa có thể vượt giới hạn bộ nhớ 64-bit (long long), cẩn thận xử lý số nguyên lớn.
```cpp
#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
int main() {
ios_base::sync_with_stdio(false); cin.tie(NULL);
freopen("BAI4.FAIRGAME.INP", "r", stdin);
freopen("BAI4.FAIRGAME.OUT", "w", stdout);
long long n, k;
cin >> n >> k;
vector<long long> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
sort(a.begin(), a.end());
vector<__int128_t> f(n);
f[0] = a[0];
__int128_t cur = 0;
for (int i = 1, j = 0; i < n; i++) {
if (a[i] == a[i - 1]) {
f[i] = f[i - 1] + a[i];
continue;
}
while (a[j] + k < a[i]) {
cur = max(cur, f[j]);
j++;
}
f[i] = cur + a[i];
}
__int128_t sum = 0, mx = 0;
for (int i = 0; i < n; i++) {
sum += a[i];
mx = max(mx, f[i]);
}
long long x = mx % MOD;
long long y = (sum - mx) % MOD;
cout << x << ' ' << y << '\n';
return 0;
}
```
# Lần 2
## Subtract
Chọn $k$ phần tử lớn nhất để giảm đi 1.
```cpp
#include <bits/stdc++.h>
using namespace std;
const int MOD = 1e9 + 7;
int main() {
freopen("BAI5.SUBTRACT.INP", "r", stdin);
freopen("BAI5.SUBTRACT.OUT", "w", stdout);
int n, k; cin >> n >> k;
vector<long long> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
sort(a.begin(), a.end());
for (int i = n - 1; i >= n - k; i--) --a[i];
long long ans = 1;
for (auto x : a) ans = x % MOD * ans % MOD;
cout << ans << '\n';
return 0;
}
```
## Pairmod
`a[i] % a[j] < a[j]`, vì thế "cặp mod" lớn nhất luôn nhỏ hơn giá trị lớn nhất của dãy `a`. Kết quả tổi ưu, chọn `a[i]` là giá trị lớn nhì và `a[j]` là giá trị lớn nhất.
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
freopen("BAI6.PAIRMOD.INP", "r", stdin);
freopen("BAI6.PAIRMOD.OUT", "w", stdout);
int q; cin >> q;
while (q--) {
int n; cin >> n;
vector<long long> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
sort(a.begin(), a.end());
long long ans = 0;
for (int i = 0; i + 1 < n; i++)
if (a[i] < a[i + 1])
ans = a[i];
cout << ans << '\n';
}
return 0;
}
```
## Findmax
Sử dụng QHĐ, gọi `dp[i][j]` là kết quả của bài toán với $a_{1..i}$ và $b_{1..j}$. Công thức truy hồi:
$dp[i][j]\xleftarrow{max}dp[i-1][j]$
$dp[i][j]\xleftarrow{max}dp[i-1][j-1]+a[i]*b[j]$
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
freopen("BAI7.FINDMAX.INP", "r", stdin);
freopen("BAI7.FINDMAX.OUT", "w", stdout);
int n, k;
cin >> n >> k;
vector<long long> a(n + 1), b(k + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= k; i++) cin >> b[i];
vector dp(n + 1, vector<long long> (k + 1, -1e18));
for (int i = 0; i <= n; i++) dp[i][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) {
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + a[i] * b[j]);
}
}
cout << dp[n][k] << '\n';
return 0;
}
```
## Splitarray
TBN
```cpp
#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
int fpow(int a, int n) {
int ans = 1;
while (n) {
if (n & 1) ans = 1ll * ans * a % MOD;
a = 1ll * a * a % MOD;
n /= 2;
}
return ans;
}
int main() {
freopen("BAI8.SPLITARRAY.INP", "r", stdin);
freopen("BAI8.SPLITARRAY.OUT", "w", stdout);
int q; cin >> q;
while (q--) {
int n;
cin >> n;
vector<int> a(n), f(n);
int positive = 1;
for (int i = 0; i < n; i++) {
cin >> a[i]; f[i] = i + 1;
if (a[i] < 0)
positive ^= (i + 1) % 2;
}
if (!positive) {
for (int i = n - 1; i >= 0; i--) {
f[i]--;
if (a[i] < 0) break;
}
}
long long ans = 1;
for (int i = 0; i < n; i++)
ans = ans * fpow((a[i] % MOD + MOD) % MOD, f[i]) % MOD;
cout << ans << '\n';
}
return 0;
}
```