# Đề THT bảng B - BRVT - 2016: Editorial
>[!Note] Thông tin
>Sau đây là lời giải của Kỳ thi Tin học trẻ bảng B tỉnh Bà Rịa - Vũng Tàu năm học 2015 - 2016.
>
>**Bạn đọc có thể nộp và chấm bài (test tự sinh)** [TẠI ĐÂY](https://chuyentin-brvt.online/contest/algi_thtb_15_24).
>
>:::info
>Chuẩn bị bởi ==team Logica==, thuộc khuôn khổ dự án [**The Algitect (ALGI Project)**](https://www.facebook.com/algitect.project)
>:::
[toc]
## Bài 1: Màu của kí tự
### Tóm tắt đề bài:
Cho xâu $s =$***"HOITHITINHOCTRETINHBARIAVUNGTAULANTHUXX"***, với kí tự **đầu** tô màu **xanh**, kí tự **2** tô màu **đỏ**, kí tự **3** tô màu **tím**, kí tự **4** tô màu **vàng**, kí tự **5** tô màu **xanh**, ...
Ta nối liên tiếp các xâu $s$ vô hạn lần đã được tô màu tạo thành xâu $t$
**Yêu cầu:** Cho $k$ là vị trí của kí tự nằm ở vị trí $k$ trong xâu $t$, tìm kí tự thứ $k$ và màu của kí tự đó, giả sử thứ tự các kí tự bắt đầu từ $0$
**Dữ liệu đảm bảo:**
- $0 < k \le 10^9$.
### Lời giải
>[!Tip] Nhận xét
>1. Xâu $s$ có tối đa 39 kí tự và xâu dễ thấy xâu $t$ có tính chu kì
>Ta biết rằng nếu 1 xâu có tính chu kì, kí tự $i$ sẽ giống kí tự $i$ $mod$ $n$
>
>2. Tương tự với màu của nó, tuy nhiên tại điểm giao giữa 2 chu kì, tức tại vị trí của kí tự cuối cùng ở chu kì $i$ và kí tự đầu của chu kì $i+1$, ta thấy rằng màu của chúng sẽ phải là T và X chứ không phải 2 màu liên tiếp nhau, do đó công thức sẽ là ($k$ $mod$ $39$) $mod$ $4$ để lấy được vị trí trong xâu $s$, từ đó tìm màu của kí tự đó
Nếu ta khai báo trước xâu $s$ và xâu $color =$ "$XDTV$", ta có thể ==sử dụng các công thức ở trên để lấy kí tự ở vị trí đó==.
**Độ phức tạp thời gian:** $O\left( 1 \right)$.
:::success
:::spoiler Code
```cpp
#include <bits/stdc++.h>
using namespace std;
int n;
string s, t;
int main() {
cin >> n;
s = "HOITHITINHOCTRETINHBARIAVUNGTAULANTHUXX";
t = "XDTV";
int m = s.size();
cout << s[n % m] << "\n" << t[(n % m) % 4];
return 0;
}
```
:::
## Bài 2: Đặt mìn
### Tóm tắt đề bài:
Cho bảng $A$ và bảng $B$ gồm $N$x$N$ phần tử chỉ gồm các số $0$ và $1$
Cần tác động một số lần để bảng $A$ trở thành bảng $B$. Mỗi lần tác động ô $[i,j]$ thì ô $[i,j]$ sẽ bị lật giá trị ($0$ thành $1$ hoặc $1$ thành $0$) các ô xung quanh nó là ô $[i-1,j]$, $[i+1,j]$, $[i,j-1]$, $[i,j+1]$ dù không được tác động cũng sẽ bị lật giá trị
**Yêu cầu:** Kiểm tra xem có tồn tại cách tác động một số ô để bảng $A$ trở thành bảng $B$ hay không. Nếu có, in ra số lần tác động tối thiểu, ngược lại in ra $-1$
**Dữ liệu đảm bảo:**
- $N \le 16$.
- $0 \le A_i, B_i \le 1$
### Lời giải
>[!Tip] Nhận xét
>Nếu toàn bộ hàng $1$ đến hàng $i-1$ đã được lật giống với bảng $B$, để hàng $i$ giống trong bảng $B$ và $i < N$, ta có thể ưu tiên sử dụng hàng $i+1$ để lật hàng $i$
Đối với hàng $N$, do không có hàng $N+1$ để lật hàng $N$, ta không còn cách để lật hàng $N$ nếu hàng này chưa giống với hàng trong bảng $B$. Do đó, với mỗi cách lật thỏa mãn, ta chỉ cần ==kiểm tra xem hàng $N$ đã được lật đúng với trong bảng $B$ hay chưa==
Đối với hàng $1$, ngoài việc được lật bằng hàng $2$, ta cũng có thể lật chính nó bằng cách tác động vào các ô trong hàng $1$, ==có $2^N$ cách để hàng $1$ tự lật==
> Sử dụng thuật toán quay lui để tự lật hàng $1$, và từ hàng $2$ đến hàng $N$, ta sẽ thao tác giống như ở trên
**Độ phức tạp thời gian:** $O\left( N^2*2^N \right)$.
:::success
:::spoiler Code
```cpp
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int n;
int a[20][20], b[20][20], grid[20][20];
int firstRowPress[20]; // Lưu trạng thái nhấn tại hàng đầu tiên
long long ans = INF;
// Hàm nhấn ô (i, j) và các ô kề (trên, dưới, trái, phải)
void press(int i, int j) {
grid[i][j] = 1 - grid[i][j];
if (i > 1) grid[i - 1][j] = 1 - grid[i - 1][j];
if (i < n) grid[i + 1][j] = 1 - grid[i + 1][j];
if (j > 1) grid[i][j - 1] = 1 - grid[i][j - 1];
if (j < n) grid[i][j + 1] = 1 - grid[i][j + 1];
}
// Hàm quay lui để thử tất cả các cấu hình nhấn ở hàng đầu tiên
void backtrack(int col) {
// Nếu đã xét hết các cột
if (col > n) {
// Copy lưới ban đầu a[][] sang lưới làm việc grid[][]
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
grid[i][j] = a[i][j];
int cnt = 0; // Đếm số lần nhấn
// Áp dụng cấu hình nhấn ở hàng đầu tiên
for (int j = 1; j <= n; j++) {
if (firstRowPress[j]) {
press(1, j);
cnt++;
}
}
// Tự động nhấn ở các hàng sau dựa vào sự khác biệt với hàng trên
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (grid[i - 1][j] != b[i - 1][j]) {
press(i, j);
cnt++;
}
}
}
// Kiểm tra lưới cuối cùng có khớp với b[][] không
bool match = true;
for (int i = 1; i <= n && match; i++) {
for (int j = 1; j <= n; j++) {
if (grid[i][j] != b[i][j]) {
match = false;
break;
}
}
}
// Nếu đúng, cập nhật đáp án nhỏ nhất
if (match) ans = min(ans, (long long)cnt);
return;
}
// Thử nhấn và không nhấn ô (1, col)
firstRowPress[col] = 1;
backtrack(col + 1);
firstRowPress[col] = 0;
backtrack(col + 1);
}
int main() {
// Nhập kích thước lưới
cin >> n;
// Nhập lưới ban đầu a[][]
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> a[i][j];
// Nhập lưới mong muốn b[][]
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> b[i][j];
// Bắt đầu quay lui từ cột 1 của hàng đầu tiên
backtrack(1);
// In kết quả
if (ans == INF)
cout << -1 << "\n"; // Không có cách nào
else
cout << ans << "\n"; // Số lần nhấn ít nhất
return 0;
}
```
:::