Lời Giải HSG Tin Tỉnh Quảng Ninh (B) - 2016
Bài 1. Số chính phương đặc biệt
Giáo sư Minh mới tìm ra loại số rất đặc biệt và đặt tên là số CPDB. Một số nguyên dương được gọi là số CPDB nếu thoả mãn hai tính chất sau:
- chia hết cho .
- có đúng ước số.
Yêu cầu
Giáo sư muốn khảo sát mật độ các số CPDB nên nhờ các bạn tham dự kỳ thi chọn học sinh giỏi lập trình giải quyết bài toán sau: “Cho hai số nguyên không âm , hãy đếm số lượng số CPDB trong đoạn .
Dữ liệu
Vào từ file văn bản CPDB.INP gồm đúng 2 số tự nhiên viết cách nhau.
Kết quả
Ghi ra file văn bản CPDB.OUT gồm số tự nhiên duy nhất là số lượng các số trong đoạn có tính chất nêu trên.
Ví dụ
| CPDB.INP | CPDB.OUT | Giải thích |
|---|---|---|
| 15 100 | 1 | Số 36 chia hết cho 3 và có 9 ước là: 1, 2, 3, 4, 6, 9, 12, 18, 36. |
Ràng buộc
- Có số test ứng với số điểm của bài có .
- Có số test ứng với số điểm của bài có .
- Có số test khác ứng với số điểm của bài có .
Lời giải
Trước hết hãy viết và chạy thuật toán trâu để tìm ra quy luật của bài toán.
Giả sử với . Ta lần lượt in ra các số thoả mãn là:
Để ý rằng đây đều là các số chính phương với căn bậc 2 của chúng là:
Từ đây ta có thể thu nhỏ khoảng tính toán từ xuống còn . Thay vì xét từ , ta chỉ cần xét từ .
Xét tính chất 1: Chia hết cho . Nếu chia hết cho thì hiển nhiên cũng phải chia hết cho .
Xét tính chất 2: Có ước. Xét chia hết cho 3, ta phân tích thừa số nguyên tố:
Để có đúng ước thì chắc chắn phải có dạng ( là số nguyên tố ) hoặc (Số ước số).
Tóm lại số CPDB sẽ thoả mãn 1 trong 2 điều sau:
- .
- , chia hết cho và là số nguyên tố.
Độ phức tạp: với .
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define nl '\n'
const int maxn = 1e4;
vector<bool> sieve(maxn, true);
void sang(){
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;
}
}
}
bool check(int i){
if (i==81) return true;
if (i==9) return false;
return (i%3==0 && sieve[i/3]);
}
int main()
{
sang();
int a, b; cin >> a >> b;
int ans = 0;
a = ceil(sqrt(a)), b = floor(sqrt(b));
for (int i=max(1, a);i<=b;i++){
if (check(i)) ans++;
}
cout << ans;
return 0;
}Bài 2. Dãy con
Cho dãy số gồm phần tử và một số nguyên dương . Hãy tìm một đoạn con liên tiếp của dãy có độ dài và có tổng các phần tử đạt giá trị lớn nhất (Độ dài của đoạn con là số lượng phần tử trên đoạn con đó).
Yêu cầu
Tính tổng các phần tử trên đoạn con theo yêu cầu như trên.
Dữ liệu
Từ tệp DAYCON.INP có cấu trúc như sau:
- Dòng thứ nhất chứa hai số nguyên dương .
- Dòng thứ hai chứa số nguyên trong đó .
- Các số nguyên trên cùng một dòng viết cách nhau bởi một dấu cách (space).
Kết quả
Ghi vào tệp DAYCON.OUT gồm một số nguyên duy nhất là tổng các phần tử trên đoạn con tìm được có giá trị lớn nhất.
Ví dụ
| DAYCON.INP | DAYCON.OUT | Giải thích |
|---|---|---|
| 5 3 -4 3 -2 6 5 | 9 | Các đoạn con có độ dài 3 là: (-4,3,-2), (3,-2,6), (-2,6,5). Nên có tổng lớn nhất là 9. |
Ràng buộc
- Có điểm tương ứng với trường hợp và .
- Có điểm tương ứng với trường hợp và .
- Có điểm tương ứng với trường hợp và .
Lời giải
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define nl '\n'
int main()
{
int n, d; cin >> n >> d;
ll a[n], current = 0;
for (int i=0;i<n;i++){
cin >> a[i];
if (i < d) current += a[i];
}
ll ans = current;
for (int i=d;i<n;i++){
current = current + a[i] - a[i-d];
ans = max(ans, current);
}
cout << ans;
return 0;
}Bài 3. Xâu đối xứng
Một trong những vấn đề quan trọng của Tin học là xâu (string). Những kiến thức, thuật toán mới luôn được tìm tòi, phát triển nhanh chóng.
Chắc hẳn bạn cũng đã từng nghe qua về xâu đối xứng. Xâu đối xứng có tính chất: đọc nó từ phải sang trái cũng thu được kết quả giống như đọc từ trái sang phải (còn gọi là xâu palindrome).
Ví dụ về xâu đối xứng như: .
Tham dự Hội thi tại trường THPT X, bạn Minh đăng ký tham dự trò chơi RCV, phần thưởng cho người chiến thắng là một chiếc thẻ nhớ 16GB từ ban tổ chức (BTC).
Trò chơi như sau: BTC đưa ra một chuỗi các kí tự chỉ bao gồm các chữ cái in thường từ đến , và một số bộ . Người chơi sẽ trả lời một bộ các câu hỏi của BTC có dạng: “xâu con gồm các kí tự liên tiếp của xâu đã cho từ vị trí đến có đối xứng hay không?”.
Yêu cầu
Bạn hãy giúp Minh dành chiến thắng cuộc thi này.
Dữ liệu
Từ tệp D_XUNG.INP có cấu trúc như sau:
- Dòng đầu ghi xâu kí tự không cho biết độ dài.
- Dòng tiếp theo ghi số nguyên dương - số câu hỏi của BTC.
- dòng tiếp theo, mỗi dòng ghi hai số nguyên .
Kết quả
Ghi vào tệp D_XUNG.OUT gồm dòng, mỗi dòng ghi tương ứng câu trả lời, nếu có ghi , ngược lại ghi .
Ví dụ
| D_XUNG.INP | D_XUNG.OUT | Giải thích |
|---|---|---|
| abxbagrednooojhggohoreomobiabba 3 1 5 10 14 28 31 | yes no yes | Xâu abxba đối xứng Xâu noooj không đối xứng Xâu abba đối xứng |
Ràng buộc
- số test có độ dài xâu
- số test có độ dài xâu .
- Trong tất cả các test độ dài xâu không quá và .
Lời giải
Ý tưởng của thuật toán là với mỗi truy vấn, ta dùng 2 con trỏ chạy từ đầu và cuối của xâu con đến khi gặp nhau.
Nếu ký tự của tại 2 con trỏ đó khác nhau thì ta kết luận được ngay đó không phải là xâu đối xứng.
Nếu 2 con trỏ đó gặp được nhau, nghĩa là không có cặp ký tự nào khác nhau trong quá trình kiểm tra thì đó là xâu đối xứng.
Với độ phức tạp khá lớn: với là độ dài xâu, thì ta chỉ được số điểm của bài.
Để lấy được số điểm còn lại, ta sử dụng kỹ thuật String Hashing.
Đầu tiên ta chọn .
Tiếp theo, ta tính trước các mảng sau:
- : luỹ thừa modulo của .
- : hash của xâu con bắt đầu tại và kết thúc tại .
- : hash của xâu con bắt đầu tại và kết thúc tại .
Xâu con là xâu đối xứng khi và chỉ khi hash của bằng với hash của đảo ngược xâu đó. Nghĩa là:
Chi phí tính toán trước chỉ là , và với mỗi truy vấn ta có thể kiểm tra đối xứng trong . Vậy độ phức tạp của thuật toán chỉ còn: .
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define nl '\n'
string s; int n;
const int maxn = 1e5+5, p = 31, m = 1e9+9;
ll prefix_hash[maxn], suffix_hash[maxn], p_pow[maxn];
void precompute(){
n = s.size();
p_pow[0] = 1;
for (int i=1;i<n;i++){
p_pow[i] = (p_pow[i-1] * p) % m;
}
prefix_hash[0] = 0;
for (int i=0;i<n;i++){
prefix_hash[i+1] = (prefix_hash[i] + (s[i] - 'a' + 1) * p_pow[i]) % m;
}
suffix_hash[n+1] = 0;
for (int i=n-1;i>=0;i--){
suffix_hash[i+1] = (suffix_hash[i+2] + (s[i] - 'a' + 1) * p_pow[n-i-1]) % m;
}
}
string solve(int l, int r){
if (l==r) return "yes";
ll left_hash = (prefix_hash[r] - prefix_hash[l-1] + m) % m;
left_hash = (left_hash * p_pow[n-l]) % m;
ll right_hash = (suffix_hash[l] - suffix_hash[r+1] + m) % m;
right_hash = (right_hash * p_pow[r-1]) % m;
return (left_hash == right_hash ? "yes" : "no");
}
int main()
{
cin >> s;
precompute();
int k; cin >> k;
while (k--){
int l, r; cin >> l >> r;
cout << solve(l, r) << nl;
}
return 0;
}