Lời Giải HSG Tin Tỉnh Quảng Ninh (B) - 2022
Bài 1. Số đặc biệt
Minh là một người rất yêu thích số nguyên tố, đồng thời cậu cũng rất thích số . Do đó, cậu ta luôn coi những số nguyên tố có tổng các chữ số chia hết cho là số đặc biệt. Lần này, thầy giáo cho Minh hai số nguyên dương và muốn Minh cho biết trong đoạn có bao nhiêu số đặc biệt.
Yêu cầu
Bạn hãy lập trình tìm câu trả lời giúp Minh.
Dữ liệu
Vào từ tệp văn bản sprime.inp có cấu trúc như sau:
- Dòng đầu tiên chứa số nguyên dương là số lượng cặp số nguyên cần trả lời.
- dòng tiếp theo, mỗi dòng chứa hai số nguyên phân biệt nhau bởi một dấu cách.
Kết quả
Ghi ra tệp văn bản sprime.out gồm dòng, mỗi dòng ghi một số nguyên là số lượng số đặc biệt trong đoạn tương ứng với thứ tự dữ liệu đầu vào.
Ví dụ
| sprime.inp | sprime.out | Giải thích |
|---|---|---|
| 2 1 10 4 20 | 1 2 | Đoạn có 1 số đặc biệt là , đoạn có 2 số đặc biệt là và . |
Ràng buộc
- Có số test ứng với số điểm của bài thoả mãn: .
- số test khác ứng với số điểm của bài thoả mãn: .
- số test ứng với số điểm của bài thoả mãn: .
Lời giải
Sử dụng Sàng Eratosthenes và mảng cộng dồn.
Giả sử là số lượng số đặc biệt từ .
⇒ Số lượng số đặc biệt trong khoảng là .
Độ phức tạp: .
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define nl '\n'
const int maxn = 3 * 1e6 + 1;
vector<int> pref(maxn + 1, 0);
vector<bool> sieve(maxn + 1, true);
void sang_nguyen_to()
{
sieve[0] = sieve[1] = false;
for (int i = 2; i * i <= maxn; i++)
{
if (sieve[i])
{
for (int j = i * i; j <= maxn; j += i)
{
sieve[j] = false;
}
}
}
for (int i = 2; i <= maxn; i++)
{
if (!sieve[i])
{
pref[i] = pref[i - 1];
continue;
}
int d_sum = 0, num = i;
while (num != 0)
{
d_sum += num % 10;
num /= 10;
}
pref[i] = pref[i - 1] + (d_sum % 5 == 0);
}
}
int main()
{
sang_nguyen_to();
int t;
cin >> t;
while (t--)
{
int l, r;
cin >> l >> r;
cout << pref[r] - pref[l - 1] << nl;
}
return 0;
}Bài 2. Luyện thi
Để chuẩn bị cho hội thi Tin học trẻ sắp tới, thầy giáo quyết định giao một bộ đề thi gồm đề thi khác nhau cho Minh để tự luyện tập.
Các đề thi được đánh số từ đến , mỗi đề thi sẽ giúp rèn luyện một số kỹ năng cho Minh để có thể đạt thành tích tốt trong hội thi Tin học trẻ.
Nhằm định hướng cho quá trình luyện tập đạt hiệu quả cao nhất, mỗi đề thi có một yêu cầu tối thiểu về trình độ kỹ năng.
Để giải được đề thi , Minh cần có trình độ kĩ năng tối thiểu là . Điều này có nghĩa là Minh có thể giải được đề thi thứ khi và chỉ khi có trình độ kỹ năng bằng hoặc lớn hơn . Nếu giải được đề thi thì trình độ kỹ năng của Minh sẽ tăng thêm một lượng là . Giả sử ban đầu, trình độ kỹ năng của Minh trước khi tập luyện là .
Các đề thi có thể được giải theo trình tự bất kỳ.
Yêu cầu
Cho các số nguyên và các cặp giá trị , hãy xác định số lượng đề thi tối đa mà Minh có thể giải được.
Dữ liệu
Vào từ tệp văn bản practice.inp có cấu trúc như sau:
- Dòng đầu tiên chứa hai số nguyên .
- Dòng thứ trong dòng tiếp theo chứa 2 số nguyên và . Các số trên cùng một dòng được phân cách nhau bởi một dấu cách.
Kết quả
Ghi ra tệp văn bản practice.out một số nguyên dương, là số đề thi tối đa mà Minh có thể giải được.
Ví dụ
| practice.inp | practice.out |
|---|---|
| 4 1 1 10 21 5 1 10 100 100 | 3 |
Giải thích: Với và các cặp giá trị tương ứng là , Minh sẽ giải đề thi 1 sau đó giải đề thi 3 và cuối cùng giải đề thi 2. Đề thi 4 Minh không giải được vì không đủ kỹ năng. Như vậy, Minh giải tối đa 3 đề thi.
Ràng buộc
- Có số test ứng với số điểm của bài thoả mãn: .
- số test còn lại ứng với số điểm của bài toán không có thêm ràng buộc nào.
Lời giải
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define nl '\n'
bool cmp(pair<ll, ll> p1, pair<ll, ll> p2)
{
return p1.first < p2.first;
}
int main()
{
int n;
cin >> n;
ll c;
cin >> c;
vector<pair<ll, ll>> v(n);
for (int i = 0; i < n; i++)
{
cin >> v[i].first >> v[i].second;
}
sort(v.begin(), v.end(), cmp);
int ans = 0;
for (auto i : v)
{
if (c >= i.first)
{
ans++;
c += i.second;
}
}
cout << ans;
return 0;
}Bài 3. Mật khẩu
Do ảnh hưởng của đại dịch Covid-19, ban tổ chức hội thi Tin học trẻ dự kiến sẽ cho các thí sinh thi theo hình thức trực tuyến.
Để tham gia thi, thí sinh sẽ được cung cấp một tài khoản và mật khẩu để đăng nhập. Để đảm bảo yếu tố bảo mật, ban tổ chức yêu cầu các thí sinh phải thay đổi mật khẩu ngay sau khi nhận được.
Mật khẩu được coi là “an toàn” nếu thỏa mãn đồng thời các điều kiện sau:
- Có độ dài tối thiểu là 6.
- Chứa ít nhất 1 chữ cái in hoa .
- Chứa ít nhất 1 chữ cái in thường .
- Chứa ít nhất 1 chữ số .
Ví dụ: các xâu kí tự: là các mật khẩu an toàn, còn các xâu kí tự: không phải là mật khẩu an toàn.
Cho một xâu kí tự chỉ gồm các loại kí tự: chữ cái in hoa, chữ cái in thường và chữ số. Hãy cho biết có bao nhiêu cặp chỉ số thoả mãn đồng thời hai điều kiện:
- , trong đó là độ dài xâu , .
- Xâu con gồm các kí tự liên tiếp từ chỉ số đến của xâu là mật khẩu an toàn.
Yêu cầu
Cho xâu kí tự , tính số lượng cặp thoả mãn hai điều kiện trên.
Dữ liệu
Vào từ tệp văn bản password.inp gồm một dòng chứa xâu kí tự .
Kết quả
Ghi ra tệp văn bản password.out một số nguyên dương là số lượng cặp chỉ số thoả mãn điều kiện xâu con là mật khẩu an toàn.
Ví dụ
| password.inp | password.out |
|---|---|
| abc123 | 0 |
| abc3456789PQ | 6 |
Ràng buộc
- Có số test ứng với số điểm của bài thoả mãn: .
- số test còn lại ứng với số điểm của bài toán không có thêm ràng buộc nào.
Lời giải
Sử dụng mảng cộng dồn và 2 con trỏ.
Khai báo các mảng:
- : số ký tự chữ cái in thường từ đầu đến vị trí .
- : số ký tự chữ cái in hoa từ đầu đến vị trí .
- : số chữ số từ đầu đến vị trí .
Cùng xem xét lại các điều kiện của mật khẩu an toàn. Giả sử một mật khẩu là dãy con của xâu đoạn , mật khẩu đó an toàn khi:
- Độ dài tối thiểu là 6: .
- Ít nhất 1 chữ cái in hoa: .
- Ít nhất 1 chữ cái in thường: .
- Ít nhất 1 chữ số: .
⇒ Ta biết nếu đoạn là một mật khẩu an toàn thì cũng phải là các mật khẩu an toàn.
Vậy thì với mỗi mật khẩu kết thúc tại vị trí , ta sẽ cần tìm vị trí gần nhất sao cho thoả mãn tất cả điều kiện biên của mật khẩu an toàn.
⇒ Số lượng mật khẩu an toàn kết thúc tại là: .
Độ phức tạp: , với là độ dài xâu .
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define nl '\n'
const int maxn = 1e5 + 1;
int thg[maxn], hoa[maxn], so[maxn];
bool safe(int i, int j)
{
return (j - i + 1 >= 6) &&
(thg[j] - thg[i - 1] >= 1) &&
(hoa[j] - hoa[i - 1] >= 1) &&
(so[j] - so[i - 1] >= 1);
}
ll solve()
{
string s;
cin >> s;
int n = s.size();
if (n < 6)
return 0;
thg[0] = hoa[0] = so[0] = 0;
for (int i = 0; i < n; i++)
{
thg[i + 1] = thg[i] + ('a' <= s[i] && s[i] <= 'z');
hoa[i + 1] = hoa[i] + ('A' <= s[i] && s[i] <= 'Z');
so[i + 1] = so[i] + ('0' <= s[i] && s[i] <= '9');
}
if (thg[n] * hoa[n] * so[n] == 0)
return 0;
int i = 1;
ll ans = 0;
for (int j = 6; j <= n; j++)
{
if (thg[j] * hoa[j] * so[j] == 0)
continue;
while (safe(i, j))
i++;
if (safe(i - 1, j))
ans += i - 1;
}
return ans;
}
int main()
{
cout << solve();
return 0;
}Bài 4. Lát nền
Viện Công nghệ thông tin đang được tu sửa và nâng cấp. Một trong những hạng mục công việc là lát lại hành lang nối từ phòng làm việc sang phòng đặt máy chủ.
Hành lang có độ rộng và độ dài được biểu thị như một lưới ô vuông gồm hàng và cột.
Để lát người ta dùng các viên gạch men loại kích thước và với số lượng dự trữ không hạn chế. Các viên gạch có thể lát dọc hoặc xoay ngang.
Trước đây hành lang được lát bằng các viên gạch kích thước và có hoặc viên gạch được lắp dưới các thiết bị điện tử. Nếu thì viên gạch ở hàng cột .
Ban Giám đốc viện không muốn lắp lại hệ thống điện tử vốn đang hoạt động rất tốt, nên yêu cầu đánh dấu viên gạch này và không được bóc lên trong quá trình lát nền.
Bộ phận thi công phàn nàn về yêu cầu trên, vì như thế sẽ hạn chế khả năng lát. Điều này làm Trưởng phòng vật tư đề nghị bộ phận lập trình tính số phương án lát nền khác nhau mà vẫn đảm bảo yêu cầu đã nêu, để bên thi công thấy có nhiều cách làm khác nhau.
Yêu cầu
Bạn hãy tính và đưa ra số phương án lát nền theo module (tức là đưa ra số dư của số phương án lát nền chia cho ).
Hai phương án gọi là khác nhau nếu tồn tại hai ô kề cạnh trong phương án này được phủ bằng một viên gạch , còn theo phương án kia thì không được phủ bằng một viên gạch .
Ví dụ: với (không có viên gạch kích thước nào được đánh dấu), ta có phương án lát nền như minh hoạ trong hình dưới đây:

Ví dụ khác: với viên gạch kích thước được đánh dấu ở vị trí (ô được tô kín trong hình vẽ), ta có phương án lát nền như minh hoạ trong hình dưới đây:

Dữ liệu
Vào từ tệp văn bản tili.inp:
- Dòng đầu tiên chứa số nguyên hoặc .
- Nếu thì dòng tiếp theo chứa hai số nguyên và .
Kết quả
Ghi ra tệp văn bản tili.out một số nguyên là số phương án lát nền theo module .
Ví dụ
| tili.inp | tili.out |
|---|---|
| 2 0 | 7 |
| 3 1 1 2 | 8 |
Ràng buộc
- Có số test ứng với số điểm của bài thoả mãn: .
- số test khác ứng với số điểm của bài thoả mãn: .
- số test khác ứng với số điểm của bài thoả mãn: .
- số test còn lại ứng với số điểm của bài không có thêm ràng buộc nào.
Lời giải
Sử dụng Quy hoạch động. Xoay nền dọc để dễ quan sát hơn.

Trường hợp 1:
Để lát nền ở hàng , thì ta phải biết ở hàng có những trường hợp nào.
Ta có những tập hợp sau:
- : Ta liệt kê được hàng có trường hợp. Gọi là trường hợp đủ; là các trường hợp thiếu.
- : Khi ta lát nền trong cách này vào những trường hợp phù hợp thì nền hàng sẽ đủ (đều tạo ra ). Cụ thể:
- .
- .
- .
- .
- .
- : Khi ta lát nền trong cách này vào những trường hợp phù hợp thì nền hàng sẽ thiếu. Cụ thể:
- .
- .
- .
- .
- .
Hãy cùng thử tính toán các trường hợp :
Các trường hợp được tạo ra 0 1 2 ⇒ Ta thấy tại thì có tổng cộng trường hợp , nghĩa là có cách để lát đủ nền bằng các viên và .
Trường hợp 2:
Hiểu được trường hợp thì rất đơn giản.
Nếu (Ô nằm cột bên phải khi xoay dọc nền), thì ta sẽ chỉ tạo ra và :
- .
- .
- .
Nếu (Ô nằm cột bên trái khi xoay dọc nền), thì ta sẽ chỉ tạo ra và :
- .
- .
- .
Tổng kết lại bài toán: Kết quả chính là số trường hợp lát đủ nền ở hàng (số được tạo ra tại hàng ).
Độ phức tạp: .
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define nl '\n'
const int maxn = 1e5 + 5, m = 1e9 + 7;
ll c1[maxn], c2[maxn], c3[maxn], c4[maxn];
int main()
{
int n, k;
cin >> n >> k;
int r = -1, c = -1;
if (k == 1)
cin >> r >> c;
c1[0] = 1, c2[0] = c3[0] = c4[0] = 0;
for (int i = 1; i <= n; i++)
{
if (i != c)
{
c1[i] = ((((2 * (c1[i - 1] % m)) % m + c2[i - 1] % m) % m + c3[i - 1] % m) % m + c4[i - 1] % m) % m;
c2[i] = (c1[i - 1] % m + c3[i - 1] % m) % m;
c3[i] = (c1[i - 1] % m + c2[i - 1] % m) % m;
c4[i] = c1[i - 1] % m;
}
else
{
if (r == 1)
{
c1[i] = (c1[i - 1] % m + c3[i - 1] % m) % m;
c2[i] = 0;
c3[i] = c1[i - 1] % m;
}
else
{
c1[i] = (c1[i - 1] % m + c2[i - 1] % m) % m;
c2[i] = c1[i - 1] % m;
c3[i] = 0;
}
c4[i] = 0;
}
}
cout << c1[n] % m;
return 0;
}