Lý do chọn đề tài Để giải các bài toán nói chung và các bài toán trong Tin học nói riêng thì việc xác định Thuật toán để giải bài toán là công việc đặc biệt quan trọng.. Việc này có ngh
Trang 1
SỞ GIÁO DỤC VÀ ĐÀO TẠO THANH HOÁ
TRƯỜNG THPT HÀM RỒNG
SÁNG KIẾN KINH NGHIỆM
MỘT SỐ BÀI TOÁN BỒI DƯỠNG HỌC SINH GIỎI
MÔN TIN HỌC 11
Người thực hiện: Nguyễn Thị Mai Hương Chức vụ: Giáo viên
SKKN thuộc lĩnh mực (môn): Tin học
THANH HOÁ NĂM 2021
Trang 2MỤC LỤC
A ĐẶT VẤN ĐỀ 1
1 Lý do chọn đề tài 1
2 Đối tượng và phạm vi nghiên cứu 1
3 Phương pháp nghiên cứu 1
B NỘI DUNG 2
Bài 1 Nộp hồ sơ đại học 2
Bài 2 Khối lập phương 4
Bài 3: Biến đổi dãy số 6
Bài 4 Look and Say 11
Bài 5 Dãy hoàn hảo 13
C KẾT LUẬN 16
Trang 31
TÊN ĐỀ TÀI:
MỘT SỐ BÀI TOÁN BỒI DƯỠNG HỌC SINH GIỎI
MÔN TIN HỌC 11
A ĐẶT VẤN ĐỀ
1 Lý do chọn đề tài
Để giải các bài toán nói chung và các bài toán trong Tin học nói riêng thì việc xác định Thuật toán để giải bài toán là công việc đặc biệt quan trọng Việc này có nghĩa là muốn giải các bài toán trong Tin học trước hết ta phải giải được các bài toán này ở góc độ Toán học, khi đó mới hình thành Thuật toán để cài đặt vào chương trình trên máy tính
Đối với học sinh các trường THPT, chương trình Tin học 11 cung cấp cho học sinh cách tư duy và giải các bài toán với sự hỗ trợ của các Ngôn ngữ lập trình
cụ thể là Ngôn ngữ lập trình PASCAL, C++ Đây cũng chính là ngôn ngữ được sử dụng để tham gia kỳ thi học sinh giỏi tỉnh môn Tin học Vấn đề đặt ra là với chương trình lớp 11 ở các nhà trường THPT chỉ được học trong 1 năm nên để sử dụng nhuần nhuyễn ngôn ngữ lập trình để tiến hành cài đặt các thuật toán học sinh phải va chạm với tương đối nhiều với một hệ thống bài tập Vì thế việc đưa ra hệ thống bài tập để học sinh có thể thực hành nhiều hơn, từ đó sử dụng linh hoạt ngôn ngữ lập trình khi cài đặt Thuật toán trên máy tính là rất cần thiết
2 Đối tượng và phạm vi nghiên cứu
- Môn Tin học lớp 11 ở trường THPT;
- Học sinh đội tuyển tin học khối 11 trường THPT Hàm Rồng;
3 Phương pháp nghiên cứu
- Phân tích, tổng hợp, khảo sát
- Đánh giá so sánh kết quả của học sinh;
Trang 42
B NỘI DUNG
Bài 1 Nộp hồ sơ đại học
Có M thí sinh đang nộp hồ sơ vào đại học Có N bàn làm việc để nhận hồ sơ của các thí sinh Bàn thứ k cần mất Tk giây để xử lí xong hồ sơ của một thí sinh Tại thời điểm 0, tất cả các bàn làm việc đều trống M thí sinh xếp hàng, và một thí sinh có thể đến ngay một bàn làm việc còn trống để nộp hồ sơ Bàn làm việc đó sẽ mất Tk giây để xử lí hồ sơ của thí sinh đó, và lại trống sau Tk giây để sẵn sàng nhận
hồ sơ tiếp theo Bạn hãy tính xem sau ít nhất bao nhiêu giây tất cả các thí sinh có thể nộp xong hồ sơ nhé
Input: Từ tệp NOPHOSO.INP gồm
- Dòng đầu tiên chứa hai số nguyên N và M (1≤N≤105; 1≤M≤109
)
- N dòng tiếp theo, dòng thứ k chứa số nguyên Tk (1≤Tk≤109
)
Output: In ra một số nguyên duy nhất là thời gian ngắn nhất để xử lí tất cả các hồ
sơ
Ví dụ:
01 2 6
7
10
18
02 7 10
3
8
3
6
9
2
4
8
Trang 53
Giải thích test 01:
7 1 Xong hồ sơ thí sinh 1, nhận hồ sơ thí sinh 3
10 2 Xong hồ sơ thí sinh 2, nhận hồ sơ thí sinh 4
14 1 Xong hồ sơ thí sinh 3, nhận hồ sơ thí sinh 5
21 1 Xong hồ sơ thí sinh 5, nhận hồ sơ thí sinh 6
Ý tưởng tham khảo:
Chặt nhị phân thời gian, với mỗi giá trị thời gian X tính coi dãy N sẽ xử lý được bao nhiêu hồ sơ và có lớn hơn hoặc bằng M hay không
Chương trình tham khảo:
#include <bits/stdc++.h>
#define Nekan "application"
#define fi first
#define se second
#define pb push_back
#define LL long long
#define pii pair<int,int>
#define forr(i,a,b) for(int i=(a);i<=(b);++i)
#define ford(i,a,b) for(int i=(a);i<(b);++i)
#define bacc(i,a,b) for(int i=(a);i>=(b); i)
const int N=1e5+5;
const long long MOD=1e9+7;
using namespace std;
int n, m;
int a[N];
void xuly()
{
cin>>n>>m;
forr(i,1,n) cin>>a[i];
LL L= 1, R= 1e18, res= 1;
Trang 64
while (L <= R)
{
LL mid= (l+r)/2;
LL sum= 0;
forr(i,1,n)
sum+= mid/a[i];
if (sum >= m)
{
R= mid-1;
ans= mid;
}
else L= mid+1;
}
cout<<res;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
freopen(Nekan".inp","r",stdin);
freopen(Nekan".out","w",stdout);
xuly();
}
Bài 2 Khối lập phương
Quà sinh nhật của Jimmy là một bộ khối lập phương xếp hình Jimmy xếp
thành n tháp, tháp thứ i có độ cao là a i (1 ≤ ai ≤ 109, 1 ≤ n ≤ 105
, i =1 ÷ n) Jimmy rất có cảm tình với số nguyên k, vì vậy dãy liên tục các tháp được coi là hài hòa nếu chúng có độ cao trung bình là k (1 ≤ k ≤ 109
)
Yêu cầu: Cho n, k và a i , i =1 ÷ k Hãy xác định dãy tháp hài hòa dài nhất, chỉ ra
tháp đầu tiên và độ dài của dãy tìm được Nếu tồn tại nhiều dãy cùng độ dài thì chỉ
ra dãy tháp có vị trí đầu nhỏ nhất Nếu không tồn tại dãy tháp thì đưa ra một số 0
Input: Vào từ file văn bản CUBICS.INP:
Trang 75
- Dòng đầu tiên chứa 2 số nguyên n và k,
- Dòng thứ 2 chứa n số nguyên a 1 , a 2 , , a n
Output: Đưa ra file văn bản CUBICS.OUT trên một dòng 2 số nguyên: độ dài của dãy tìm được và số thứ tự của tháp đầu tiên hoặc một số 0 nếu không tồn tại dãy
Ví dụ:
CUBICS.INP CUBICS.OUT
5 3
1 2 3 4 6
3 2
Ý tưởng tham khảo:
Trừ mỗi phần tử A[i] đi K để đổi thành bài toán tìm đoạn con dài nhất có tổng bằng 0, bởi một đoạn độ dài L có tổng bằng 0 nghĩ là tổng của đoạn đó ban đàu bằng K*L => đoạn có có trung bình cộng là K
Chương trình tham khảo:
#include <bits/stdc++.h>
#define Nekan "cubics"
#define fi first
#define se second
#define pb push_back
#define LL long long
#define pii pair<int,int>
#define forr(i,a,b) for(int i=(a);i<=(b);++i)
#define ford(i,a,b) for(int i=(a);i<(b);++i)
#define bacc(i,a,b) for(int i=(a);i>=(b); i)
const int N=1e5+5;
const long long MOD=1e9+7;
using namespace std;
int n, k, ans, L;
LL a[N];
map <LL, int> m;
void xuly()
{
cin>>n>>k;
LL s= 0;
forr(i,1,n)
{
cin>>a[i];
a[i]-= k;
a[i]+= a[i-1];
Trang 86
if (a[i] != 0 && m[a[i]] == 0) m[a[i]] = i;
if (i - m[a[i]] >= ans)
{
if (i - m[a[i]] > ans)
L= m[a[i]] + 1;
else if (L > m[a[i]] + 1) L= m[a[i]] + 1;
ans= i - m[a[i]];
}
}
if (ans == 0) cout<<"0";
else
cout<<ans<<" "<<L;
}
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
freopen(Nekan".inp","r",stdin);
freopen(Nekan".out","w",stdout);
xuly();
}
Bài 3: Biến đổi dãy số
Cho dãy số nguyên Một phép biến đổi là tăng giá trị của phần tử nào đó lên 1 đơn vị Hãy tìm cách thực hiện ít phép biến đổi nhất để thu được một
dãy số mới trong đó có h số liên tiếp nhau tạo thành một dãy tăng dần từ 1 đến h, tức là tồn tại một vị trí i sao cho
Input: Vào từ file văn bản INCREASING.INP:
- Dòng đầu tiên chứa hai số nguyên n và h (
- Dòng thứ hai chứa n số nguyên
Output: Ghi ra file văn bản INCREASING.OUT một số nguyên duy nhất là số
phép biến đổi ít nhất cần thực hiện để có một dãy mới theo yêu cầu Nếu không có cách biến đổi thì in ra -1
Ví dụ:
INCREASING.INP INCREASING.OUT
4 3
1 1 0 2
3
Trang 97
Ý tưởng tham khảo:
Duyệt qua tất cả các đoạn liên tiếp độ dài h, ta sẽ kiểm tra xem đoạn [i,i+h-1] có thể đưa về dạng như yêu cầu hay không , nếu được thì cập nhật kết quả
Để kiểm tra đoạn [i, i+h-1] có thỏa hay không ta có vài nhận xét như sau: Chỉ có thao tác tăng 1 phần tử bất kì lên 1 đơn vị, cho nên điều kiện cần để đoạn [i, i+h-1] có thể đưa về đoạn đúng yêu cầu là a[i] <= 1; a[i+1] <= 2; a[i+2]<=3; … a[i+h-1] <= h
Phân tích thêm 1 chút ta có a[i] - 1 <= 0 ; a[i+1] - 2 <= 0; a[i+2] - 3 <= 0; (1)
Để đảm bảo điều kiện (1), thì max (a[i] - 1, a[i+1]-2, a[i+2]-3, , a[i+h-1]-h) phải <= 0
Để giảm a[i] đi 1, a[i+1] đi 2, a[i+2] đi 3, ta có nhận xét sau :
sử dụng sliding window : với h = 4 và dãy : a b c d e f g
với đoạn [1,4] ta có dãy mới : a-1 b-2 c-3 d-4 e f g
với đoạn [2,5] ta có dãy mới : a-0 b-1 c-2 d-3 e-4 f g
với đoạn [3,5] ta có dãy mới : a b-0 c-1 d-2 e-3 f-4 g
Như vậy, khi đi từ đoạn [i, i+h-1] sang đoạn [i+1, i+h] , tất cả các phần tử từ
i đến i + h - 1 được tăng lên 1, và phần tử i + h bị trừ đi h
Nếu max(a[i]) của đoạn [i, i+h-1] <= 0 thì đoạn này có thể đưa về theo yêu cầu, lúc này chi phí sẽ là
(1 - a[i]) + (2 - a[i+1]) + (3 - a[i+2]) + + (h - a[i+h-1])
= 1 + 2 + 3 + + h - (a[i] + a[i+1]+ a[i+2] + + a[i+h-1])
= h * (h+1) / 2 - (F[i + h - 1] - F[i-1])
trong đó F[i] = F[i-1] + a[i]
Chương trình tham khảo:
#include <bits/stdc++.h>
#define EL cout<<'\n'
#define pli pair<ll,int>
#define pll pair<ll,ll>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define sz(a) int(a.size())
Trang 108
#define FU(x,a,b) for(int x=int(a);x<=int(b);x++)
#define FD(x,a,b) for(int x=int(a);x>=int(b);x )
#define PROB "INCREASING"
using namespace std;
typedef long long ll;
typedef double db;
template <typename T>
inline void read(T& x){
bool Neg = false;
char c;
for (c = getchar(); c < '0' | c > '9'; c = getchar())
if (c == '-') Neg = !Neg;
x = c - '0';
for (c = getchar(); c >= '0' && c <= '9'; c = getchar())
x = x * 10 + c - '0';
if (Neg) x = -x;
}
template <typename T>
inline void write(T x)
{
if (x < 0)
{
putchar('-'); x = -x;
}
T p = 1;
for (T temp = x / 10; temp > 0; temp /= 10) p *= 10;
for (; p > 0; x %= p, p /= 10) putchar(x / p + '0');
}
void setIO() {
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
if(fopen(PROB".inp", "r")){
freopen(PROB".inp", "r",stdin);
freopen(PROB".out", "w",stdout);
}
}
const int N = 200002;
Trang 119
int n, h, a[N];
ll f[N], st[N * 4], lz[N * 4];
void readinp(){
cin >> n >> h;
FU(i, 1, n) cin >> a[i];
}
void down(int id){
ll t = lz[id];
st[id * 2]+= t;
lz[id * 2]+= t;
st[id * 2 + 1]+= t;
lz[id * 2 + 1]+= t;
lz[id] = 0;
}
void upd (int id, int l, int r, int u, int v, ll g){
if (r < u || v < l) return;
if (u <= l && r <= v){
st[id]+= g;
lz[id]+= g;
return;
}
down(id);
int mid = (l + r) / 2;
upd(id * 2, l, mid, u, v, g);
upd(id * 2 + 1, mid + 1, r, u, v, g);
st[id] = max(st[id * 2], st[id * 2 + 1]);
}
ll get (int id, int l, int r, int u, int v){
if (r < u || v < l) return -1e18;
if (u <= l && r <= v) return st[id];
down(id);
int mid = (l + r) / 2;
ll t1 = get(id * 2, l, mid, u, v);
ll t2 = get(id * 2 + 1, mid + 1, r, u, v);
return max(t1, t2);
}
Trang 1210
void build (int id, int l, int r){
if (l == r){
st[id] = a[l];
return;
}
int mid = (l + r) / 2;
build(id * 2, l, mid);
build(id * 2 + 1, mid + 1, r);
st[id] = max(st[id * 2], st[id * 2 + 1]);
}
void solve(){
FU(i, 1, n) f[i] = f[i-1] + ll(a[i]);
build(1, 1, n);
ll res = 1e18;
FU(i, 1, h - 1) upd(1, 1, n, i, i, - i);
FU(i, h, n){
int j = i - h + 1;
upd(1, 1, n, i, i, -h);
if (get(1, 1, n, j, i) <= 0){
res = min(res, ll(h) * ll(h+1) / 2LL - f[i] + f[j-1]);
}
upd(1, 1, n, j, i, 1);
}
cout << (res == 1e18 ? -1 : res);
}
int main(){
setIO();
int t = 1;
// cin >> t;
while (t ){
readinp();
solve();
}
}
Trang 1311
Bài 4 Look and Say
Học đã giỏi, Bo tự nghĩ ra cho mình một cách viết số mới Từ trái sang phải,
Bo đếm số lượng chữ số liên tiếp bằng nhau và ghi số lượng sô chữ số đó và kế tiếp theo là chữ số tương ứng Làm lần lượt như vậy cho tới hết số Ví dụ, số
Bo viết lại thành Số , Bo lại viết thành
Yêu cầu: Cho dãy chữ số, hãy cho biết số Bo viết ra tương ứng
Input: Vào từ file LOOKSAY.INP theo quy tắc:
- Dòng đầu tiên là số – số lượng test
- T nhóm dòng tiếp theo, mỗi dòng gồm một dãy chữ số độ dài không quá
1000
Output: ghi ra file LOOKSAY.OUT dòng là kết quả các test tương ứng theo thứ
tự
Ví dụ:
LOOKSAY.INP LOOKSAY.OUT
2
122344111
1111111111
1122132431
101
Ý tưởng tham khảo:
Khởi tạo i: = 0;
(1) Nếu i >= |S| dừng chương trình; nếu không thực hiện lệnh (2)
(2) Tìm vị trí j lớn nhất có thể (j >= i) sao cho tất cả kí tự từ i đến j đều giống nhau Khi đó xuất (j – i + 1) dưới dạng xâu và kèm theo kí tự S[i] ở ngay sau
(3) Gán i:= j + 1 , tiếp tục thực hiện lệnh (1)
Chương trình tham khảo:
#include <bits/stdc++.h>
#define EL cout<<'\n'
#define sz(a) int(a.size())
#define FU(x,a,b) for(int x=int(a);x<=int(b);x++)
#define FD(x,a,b) for(int x=int(a);x>=int(b);x )
#define PROB "LOOKSAY"
using namespace std;
void setIO() {
Trang 1412
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
if(fopen(PROB".inp", "r")){
freopen(PROB".inp", "r",stdin);
freopen(PROB".out", "w",stdout);
}
}
string s;
void readinp(){
cin >> s;
}
string To_string(int x){ /// Hàm chuyển số sang xâu
string res = "";
while (x != 0){
res = res + char(x % 10 + '0');
x = x / 10;
}
reverse(res.begin(), res.end());
return res;
}
void solve(){
int i = 0;
while (i < sz(s)){
int j = i;
char c = s[i];
while (j < sz(s) && s[j] == c)
j++;
j ;
cout << To_string(j - i + 1) << c;
i = j + 1;
}
EL;
}
Trang 1513
int main(){
setIO();
int t = 1;
cin >> t;
while (t ){
readinp();
solve();
}
}
Bài 5 Dãy hoàn hảo
Cho dãy n phần tử nguyên dương, dãy con của dãy đã cho là dãy các phần tử liên tiếp của dãy đó Độ hoàn hảo của một dãy con là trung bình cộng của dãy con
đó Rõ ràng ta có thể thấy rằng dãy có độ hoàn hảo lớn nhất chính là số lớn nhất trong dãy, như vậy thì quá đơn giản để tìm được dãy con có độ hoàn hảo lớn nhất
Vì vậy ở đây ta chỉ xét những dãy có tổng của các phần tử không nhỏ hơn k
Yêu cầu: Cho n, k và dãy n phần tử, tìm dãy con có độ hoàn hảo lớn nhất
Input: Vào từ file văn bản SEQUENCE.INP gồm:
- Dòng đầu tiên chứa hai số nguyên dương và ( ,
)
- Dòng thứ hai chứa n số nguyên dương a i các phần tử của dãy đã
Output: Ghi ra file văn bản SEQUENCE.OUT một số duy nhất là kết quả tìm
được, làm tròn xuống thành số nguyên
Ví dụ:
SEQUENCE.INP SEQUENCE.OUT
5 6
1 5 2 4 3
3
Ý tưởng tham khảo:
Chặt nhị phân với giá trị trung bình x
Ta có: length * x = sum nên: length >= k/x
Trang 1614
Đưa bài toán về kiểm tra xem có dãy con liên tiếp độ dài lớn hơn hoặc bằng k/x không?
Chương trình tham khảo:
#include <bits/stdc++.h>
using namespace std;
#define PROB "SEQUENCE"
void Fi(){
freopen(PROB".inp", "r", stdin);
freopen(PROB".out", "w", stdout);
}
typedef long long ll;
const int N = 1e5 + 1;
int a[N], tmp[N], n, k;
bool check(int x){
int minl = k / x + (k % x != 0);
if(minl > n) return false;
for(int i = 1; i <= n; ++i){
tmp[i] = a[i] - x;
}
ll sum = 0, last = 0;
for(int i = 1; i <= minl; ++i){
sum += tmp[i];
}
for(int i = minl + 1; i <= n; ++i){
last += tmp[i - minl];
sum += tmp[i];
if(last < 0){
sum -= last;
last = 0;
}
if(sum >= 0) return true;
}
return false;
}
int main()