Sacombank me

Ấn tượng

BÍ ẨN KHÔNG XA LẠ
Xa lạ vì ta không quá chú tâm đến nó

VÕ NHẬT TRƯỜNG


Tôi : Võ Nhật Trường
Sinh ngày: 01/05/1982
Quê quán: Tam Quan Bắc -Hoài Nhơn –Bình Định
Chuyên môn: Tin học
Sở thích: Học tập, vui chơi và giao lưu cùng bạn bè !
Email liên hệ:
Sunrise.tqb@gmail.com
Sunrise.tqb@hotmail.com
Sunrise1582@yahoo.com.vn
Sunrise_tqb@yahoo.com
Sunrise1582@outlook.com
Truongthienthutqb@gmail.com
Truongthienthutqb@outlook.com.vn
annhientqb@gmail.com
Vonhattruongqn123@gmail.com
Voluatqb@gmail.com
NhatTruongH343W5V@gmail.com
NhatTruongHp331@outlook.com
Vonhattruongsunface@outlook.com
Sunrise.tqb@outlook.com.vn
Vonhattruong377@outlook.com
Truongluathienthu@yahoo.com
Truongthienthuttt@yahoo.com
VonhatTruongHnb@yahoo.com
NhatTruongH343@yahoo.com
Số TK Aribank: 4307215007539
Số BHXH:5208007390
Điện thoại: 0985297377
Điện thoại: 0965661247
Điện thoại: 0374125377
Điện thoại: 0838608577
CCCD 052082016995
Số CMT:211725206

Tài nguyên dạy học

Ngẫu nhiên

The_co_hoa_0127.jpg Ki_niem_2022.flv Vo_Anh_Thien_tap_the_duc_2022.flv Vui2022Truong_Thien_Thu.flv Bieu_dien_hay_03.flv Cong_nghe_hay_04.flv Cong_nghe_hay_03.flv Cong_nghe_hay_02.flv Cong_nghe_hay_01.flv Bieu_dien_hay_02.flv Bieu_dien_hay_01.flv Nau_an_02.flv Nau_an_01.flv Ntc2018_0141.JPG Nhung_sang_che_tuyet_voi_03.flv Gia_dinh_hai_nao.flv U23VN_vao_chung_ket_AFC_2018.flv Song_dep_23.flv

Cảm xúc






Sắp xếp dữ liệu

Điều tra ý kiến

Đánh giá của bạn về trang này?
Tốt
Khá
Trung bình
Ý kiến khác

Thống kê

  • truy cập   (chi tiết)
    trong hôm nay
  • lượt xem
    trong hôm nay
  • thành viên
  • Thành viên trực tuyến

    5 khách và 0 thành viên

    Võ Nhật Trường


    >
    >
    >

    Thank You

    CHÀO CÁC BẠN

    Hân hạnh chào đón các bạn đến với Website của Võ Nhật Trường -Tam Quan Bắc- Hoài Nhơn- Bình Định.

    LƯU Ý:

    NGHIÊM CẤM CÁC HÀNH VI SAO CHÉP, DOWNLOAD TRÁI PHÁP LUẬT.
    OFFICIAL COPY, DOWNLOAD ACTIVITIES PROHIBITED.

    Dịch Google

    Một số chủ đề BDHS giỏi tin.pdf

    Wait
    • Begin_button
    • Prev_button
    • Play_button
    • Stop_button
    • Next_button
    • End_button
    • 0 / 0
    • Loading_status
    Nhấn vào đây để tải về
    Báo tài liệu có sai sót
    Nhắn tin cho tác giả
    Nguồn:
    Người gửi: Võ Nhật Trường (trang riêng)
    Ngày gửi: 09h:40' 24-12-2025
    Dung lượng: 4.5 MB
    Số lượt tải: 0
    Số lượt thích: 0 người
    Gợi ý một số dạng bài HSG hay gặp


    Số học: sàng nguyên tố, ước – bội, phân tích thừa số, modulo



    Mảng & chuỗi: prefix sum, two pointers, sliding window



    Quy hoạch động: dãy con, chia tiền, xâu con chung



    Đồ thị: BFS/DFS, đường đi ngắn nhất, cây



    Tổ hợp – đếm: hoán vị, chỉnh hợp, phân hoạch số

    Bài HSG thường xoay quanh:


    Sàng nguyên tố



    Mảng cộng dồn



    Hai con trỏ



    Quy hoạch động cơ bản



    Đệ quy + chia để trị



    Đồ thị cơ bản (DFS/BFS)

    #include
    #define N 10000000

    // Gộp toàn bộ thư viện STL
    // Giới hạn tối đa cho mảng sàng nguyên tố

    using namespace std;
    int nt[N];
    int l, r;

    // Mảng đánh dấu số nguyên tố: nt[x] = 1 nếu x là số nguyên tố
    // Khoảng cần xét

    // Hàm sàng nguyên tố Eratosthenes
    void sangnt(int n)
    {
    fill(nt + 2, nt + n + 1, 1); // Gán nt[2..n] = 1 (giả sử ban đầu tất cả đều là số nguyên tố)
    for (int i = 2; i * i <= n; i++) // Duyệt đến sqrt(n)
    if (nt[i] == 1)

    // Nếu i là số nguyên tố

    for (int j = i * i; j <= n; j += i)

    nt[j] = 0;

    // Đánh dấu các bội số của i không phải số nguyên tố

    }
    // Hàm tính tổng chữ số của x
    int tongcs(int x)
    {
    int s = 0;
    while (x > 0)
    {
    s += x % 10; // Lấy chữ số cuối
    x /= 10;

    // Bỏ chữ số cuối

    }
    return s;
    }
    int main()
    {
    freopen("password.inp", "r", stdin); // Đọc từ file
    freopen("password.out", "w", stdout); // Ghi ra file
    cin >> l >> r;

    // Nhập khoảng [l, r]

    sangnt(r);

    // Sàng nguyên tố đến r

    // Duyệt tất cả các số từ 1 đến r
    { nên for (int x = l; x <= r; x++)
    if (nt[x] == 1 && nt[tongcs(x)] == 1)
    cout << x << " "; }
    for (int x = 1; x <= r; x++)
    // Điều kiện mật khẩu:
    // 1. x là số nguyên tố

    // 2. Tổng chữ số của x cũng là số nguyên tố
    if (nt[x] == 1 && nt[tongcs(x)] == 1)
    cout << x << " ";

    return 0;
    }
    //////////////////////////
    1. #include là gì?
    Đây là một header tổng hợp, chỉ có trên trình biên dịch GCC (g++), không phải chuẩn của C++.
    Nó chứa gần như tất cả các thư viện STL:


    … và rất nhiều nữa.



    fill

    là hàm có sẵn trong C++, nằm trong thư viện .
    Nó không phải thủ tục bạn tự viết, mà là hàm chuẩn của STL.

    fill(begin, end, value);
     begin: con trỏ hoặc iterator bắt đầu


    end: con trỏ hoặc iterator kết thúc (không bao gồm vị trí này)



    value: giá trị muốn gán

    #define N 10000000
    Đây là macro trong C++.
    Nó có nghĩa là:


    Mỗi khi gặp chữ N, trình biên dịch sẽ thay bằng 10000000.

    ////////////////
    Chương trình nhận vào một chuỗi ký tự, trong đó có thể chứa chữ cái và chữ số lẫn lộn, ví dụ:
    ab12c3d12x7
    Nhiệm vụ:


    Tách tất cả các số xuất hiện trong chuỗi



    Đánh dấu số nào đã xuất hiện



    Đếm xem có bao nhiêu số khác nhau

    for (; j < s.size() && ... ; j++)
    Nghĩa là: không khởi tạo gì trong vòng for
    → vì biến j đã được khởi tạo từ trước: viết đầy đủ sẽ thành
    for (int j = i; j < s.size() && ...; j++)
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    1. ios::sync_with_stdio(false); — Tắt đồng bộ C và C++
    Ý nghĩa:
    C++ có hai hệ thống nhập/xuất:


    cin, cout (C++)



    scanf, printf (C)

    Mặc định, C++ đồng bộ hai hệ thống này để tránh lỗi, nhưng điều đó làm chương trình chậm
    hơn.
    Khi bạn viết:
    ios::sync_with_stdio(false);
    Bạn tắt đồng bộ, nghĩa là:


    cin và cout chạy độc lập



    tốc độ tăng lên đáng kể (nhanh gấp 2–3 lần)

    ⚠ Lưu ý quan trọng:
    Khi đã tắt đồng bộ:


    KHÔNG được dùng lẫn lộn cin với printf, hoặc cout với scanf.

    2. cin.tie(nullptr); — Bỏ buộc cin với cout
    Mặc định:


    cin bị “buộc” với cout



    Mỗi lần gọi cin, hệ thống sẽ tự động flush (đẩy ra màn hình) cout

    Điều này an toàn nhưng làm chậm.
    Khi bạn viết:
    cin.tie(nullptr);
    Bạn tháo buộc này ra.
    Kết quả:


    cin không còn ép cout phải flush nữa



    tốc độ nhập/xuất tăng lên

    MỤC TIÊU CỦA CHƯƠNG TRÌNH
    Chương trình nhận vào một chuỗi ký tự, trong đó có thể chứa chữ cái và chữ số lẫn lộn, ví dụ:
    ab12c3d12x7
    Nhiệm vụ:


    Tách tất cả các số xuất hiện trong chuỗi



    Đánh dấu số nào đã xuất hiện



    Đếm xem có bao nhiêu số khác nhau

    Ví dụ trên → các số xuất hiện:
    12, 3, 7 → có 3 số khác nhau.
    PHÂN TÍCH TỪNG DÒNG LỆNH
    #include
    Gộp tất cả thư viện STL vào một file (chỉ có trên g++).
    Giúp code ngắn hơn.
    const int MAXN = 1001;
    Đặt giới hạn tối đa cho số có thể xuất hiện trong chuỗi.
    → Các số trong chuỗi được giả sử nhỏ hơn 1000.
    string s;
    Chuỗi đầu vào.

    int imam[MAXN];
    Mảng đánh dấu:


    imam[x] = 1 nghĩa là số x đã xuất hiện trong chuỗi



    imam[x] = 0 nghĩa là chưa xuất hiện

    Tối ưu nhập xuất
    ios::sync_with_stdio(false); cin.tie(nullptr);
    Giúp chương trình chạy nhanh hơn (chuẩn competitive programming).
    Đọc file nếu có
    if (fopen("mm.inp", "r")) { freopen("mm.inp", "r", stdin); freopen("mm.out", "w", stdout); }
    Nếu tồn tại file mm.inp, chương trình sẽ:


    đọc từ file mm.inp



    ghi ra file mm.out

    Nếu không có file → đọc từ bàn phím bình thường.
    Đọc chuỗi
    cin >> s;
    Lưu ý: chỉ đọc đến dấu cách đầu tiên.
    (Đề bài có thể đảm bảo chuỗi không chứa khoảng trắng.)
    VÒNG LẶP CHÍNH – TÁCH SỐ TRONG CHUỖI
    for (int i = 0; i < (int)s.size(); i++)
    Duyệt từng ký tự trong chuỗi.
    Bỏ qua chữ cái
    if (s[i] >= 'a' && s[i] <= 'z') continue;
    Nếu là chữ thường → bỏ qua.
    (Chỉ xử lý chữ số.)
    Bắt đầu ghép số
    int broj = 0; int j = i;



    broj = số đang được ghép (tên tiếng Croatia, nghĩa là “number”)



    j = vị trí chạy để ghép số

    Ghép các chữ số liên tiếp thành một số nguyên
    for (; j < (int)s.size() && s[j] >= '0' && s[j] <= '9'; j++) { broj *= 10; broj += s[j] - '0'; }
    Ví dụ chuỗi: ab123cd
    Khi gặp 1:


    broj = 1



    gặp 2 → broj = 12



    gặp 3 → broj = 123

    Vòng lặp dừng khi gặp ký tự không phải số.
    Đánh dấu số đã xuất hiện
    imam[broj] = 1;
    Ví dụ: nếu số 123 xuất hiện → imam[123] = 1.
    Nhảy i đến cuối số vừa đọc
    i = j - 1;
    Giúp tránh đọc lại các chữ số đã xử lý.
    ĐẾM BAO NHIÊU SỐ KHÁC NHAU
    int rj = 0; for (int i = 0; i < MAXN; i++) { rj += imam[i]; }
    Vì mỗi số chỉ được đánh dấu 0 hoặc 1, nên tổng chính là số lượng số khác nhau.
    🖨

    In kết quả

    cout << rj << endl;
    TÓM TẮT NGẮN GỌN CHO GIÁO VIÊN
    Chương trình:
    1. Đọc chuỗi
    2. Tách các số liên tiếp trong chuỗi

    3. Đánh dấu số nào xuất hiện
    4. Đếm số lượng số khác nhau
    5. In kết quả
    Thuật toán chạy rất nhanh, phù hợp bài HSG.
    #include

    // Gộp toàn bộ thư viện STL (chỉ có trên g++)

    using namespace std;
    const int MAXN = 1001;
    string s;

    // Giới hạn số tối đa có thể xuất hiện trong chuỗi

    // Chuỗi đầu vào

    int imam[MAXN];

    // Mảng đánh dấu: imam[x] = 1 nghĩa là số x đã xuất hiện

    int main()
    {
    ios::sync_with_stdio(false); // Tăng tốc độ nhập/xuất (tắt đồng bộ với stdio C)
    cin.tie(nullptr);

    // Không buộc cin phải flush cout → nhanh hơn

    // Nếu tồn tại file mm.inp → đọc từ file, ghi ra file mm.out
    if (fopen("mm.inp", "r"))
    {
    freopen("mm.inp", "r", stdin);
    freopen("mm.out", "w", stdout);
    }
    cin >> s;

    // Đọc chuỗi (không chứa khoảng trắng)

    // Duyệt từng ký tự trong chuỗi
    for (int i = 0; i < (int)s.size(); i++)
    {
    // Nếu ký tự là chữ cái thường → bỏ qua
    if (s[i] >= 'a' && s[i] <= 'z')

    continue;
    int broj = 0;
    int j = i;

    // Biến để ghép số (broj = "number" theo tiếng Croatia)
    // j bắt đầu từ vị trí i

    // Ghép các chữ số liên tiếp thành một số nguyên
    for (; j < (int)s.size() && s[j] >= '0' && s[j] <= '9'; j++)
    {
    broj *= 10;

    // Dịch trái (nhân 10)

    broj += s[j] - '0'; // Cộng chữ số hiện tại
    }
    imam[broj] = 1;
    i = j - 1;

    // Đánh dấu số này đã xuất hiện
    // Nhảy i đến cuối dãy số vừa đọc

    }
    int rj = 0;

    // rj = kết quả: số lượng số khác nhau

    // Đếm xem có bao nhiêu số đã được đánh dấu
    for (int i = 0; i < MAXN; i++)
    {
    rj += imam[i];

    // imam[i] = 1 → số đó xuất hiện

    }
    cout << rj << endl;

    // In kết quả

    return 0;
    }
    Chương trình thực hiện:
    1. Đọc chuỗi gồm chữ cái và chữ số lẫn lộn
    2. Tách tất cả các số nguyên xuất hiện trong chuỗi
    3. Đánh dấu số nào đã xuất hiện
    4. Đếm số lượng số khác nhau

    5. In kết quả

    //////////////////////////////
    getline(cin, str); dùng để làm gì?
    Lệnh này đọc cả một dòng văn bản từ bàn phím (hoặc file) và lưu vào biến chuỗi str.
    Khác với cin >> str, getline sẽ:


    Đọc toàn bộ dòng, kể cả khoảng trắng



    Dừng lại khi gặp ký tự xuống dòng (\n)



    Không bỏ qua khoảng trắng

    cin >> n;
    cin.ignore(); // bỏ ký tự '\n' còn lại
    getline(cin, s);
    /////////////////////////////////////////
    #include
    #define K 200000

    // Gộp toàn bộ thư viện STL (tiện cho bài thi)
    // Giới hạn tối đa cho chỉ số Fibonacci cần tính

    #define MOD 10000000007
    #define ll long long

    // Số modulo rất lớn để tránh tràn số

    // Viết tắt kiểu long long

    using namespace std;
    ll F[K + 2], ans;
    int n, k;

    // Mảng Fibonacci và biến lưu tổng
    // Hai số đầu vào

    int main()
    {
    freopen("flashback.inp", "r", stdin); // Đọc từ file (nếu có)
    freopen("flashback.out", "w", stdout); // Ghi ra file
    cin >> n >> k;

    // Nhập n và k

    F[1] = F[2] = 1;

    // Khởi tạo hai số Fibonacci đầu tiên

    ans = 2;

    // F[2] = 1 và F[0] không dùng → tổng ban đầu = 1 + 1 = 2

    // Tính Fibonacci từ F[3] đến F[2*k]
    for (int i = 3; i <= 2 * k; i++)
    {
    F[i] = (F[i - 1] + F[i - 2]) % MOD; // Công thức Fibonacci chuẩn
    if (i % 2 == 0)

    // Nếu chỉ số i là số chẵn

    ans = (ans + F[i]) % MOD;

    // Cộng vào tổng

    }
    // Kết quả cuối cùng là tổng Fibonacci chẵn nhân với n
    cout << (ans * n) % MOD;

    return 0;
    }
    ///////////////
    1. Tránh tràn số (integer overflow)
    Khi nào bị tràn số?
    Khi giá trị vượt quá giới hạn kiểu dữ liệu:


    int tối đa khoảng 2 tỷ



    long long tối đa khoảng 9×10¹⁸



    Fibonacci, lũy thừa, nhân lớn… rất dễ vượt giới hạn

    ✔ Cách tránh tràn số


    Dùng kiểu lớn hơn: long long, unsigned long long



    Dùng modulo:
    Ví dụ:

    F[i] = (F[i-1] + F[i-2]) % MOD;


    → giữ số luôn nhỏ hơn MOD → không bao giờ tràn



    Dùng thư viện big integer (nếu cần số cực lớn)



    C++: boost::multiprecision::cpp_int



    Python: tự động hỗ trợ số lớn

    2. Tránh tràn bộ nhớ (memory overflow)
    Khi nào bị tràn bộ nhớ?


    Khai báo mảng quá lớn (ví dụ int a[10^9])



    Dùng đệ quy quá sâu



    Cấp phát động không kiểm soát

    ✔ Cách tránh


    Dùng vector thay vì mảng tĩnh

    vector a(n);


    Chỉ cấp phát đúng kích thước cần thiết



    Tránh đệ quy sâu → chuyển sang vòng lặp hoặc dùng stack tự quản lý



    Không tạo mảng trong hàm quá lớn
    (stack chỉ khoảng 1–8 MB)

    3. Tránh quá tải CPU (chạy quá lâu)
    Khi nào bị quá tải?


    Vòng lặp quá lớn: O(n²), O(n³)



    Thuật toán không tối ưu



    Duyệt lại dữ liệu nhiều lần không cần thiết

    ✔ Cách tránh


    Tối ưu thuật toán:
    o

    Dùng mảng cộng dồn

    o

    Dùng sàng nguyên tố

    o

    Dùng quy hoạch động

    o

    Dùng hai con trỏ



    Giảm số vòng lặp lồng nhau



    Dùng cấu trúc dữ liệu phù hợp
    (set, map, priority_queue…)

    4. Tránh quá tải I/O (nhập xuất quá chậm)
    Khi nào bị chậm?


    In/đọc hàng triệu dòng bằng cin/cout

    ✔ Cách tăng tốc


    Dùng:

     ios::sync_with_stdio(false); cin.tie(nullptr);


    Hoặc dùng scanf/printf nếu cần tốc độ cao

    🛡 5. Tránh lỗi logic gây crash chương trình
    ✔ Kiểm tra chỉ số mảng
    if (i >= 0 && i < n)
    ✔ Tránh chia cho 0
    if (b != 0) x = a / b;
    ✔ Kiểm tra dữ liệu đầu vào


    Không đọc chuỗi rỗng



    Không dùng biến chưa khởi tạo

    Tóm tắt 1 câu dễ nhớ
    Muốn tránh tràn số → dùng modulo.
    Muốn tránh tràn bộ nhớ → dùng vector.
    Muốn tránh quá tải CPU → tối ưu thuật toán.
    Muốn tránh chậm I/O → bật sync off + tie nullptr.
    //////////////////////////
    1. Khi đã dùng freopen, bạn xử lý dữ liệu y như nhập từ bàn phím
    Ví dụ:
    freopen("password.inp","r",stdin); cin >> x;

    Lúc này:


    cin >> x; không đọc từ bàn phím



    mà đọc giá trị từ file password.inp



    và gán vào biến x đúng kiểu dữ liệu bạn khai báo (int, long long, string…)

    Bạn xử lý biến x hoàn toàn bình thường, không khác gì nhập từ bàn phím.
    2. Ví dụ minh họa
    Giả sử file password.inp chứa:
    10 50
    Code:
    int l, r; freopen("password.inp","r",stdin); cin >> l >> r;
    Sau khi chạy:


    l = 10



    r = 50

    Bạn xử lý tiếp:
    for (int i = l; i <= r; i++) cout << i << " ";
    3. Tóm lại


    freopen("file.inp", "r", stdin); → thay nguồn nhập bằng file



    cin >> x; → đọc dữ liệu từ file vào biến x



    Bạn xử lý x như biến bình thường



    Không cần lệnh đặc biệt nào khác

    /////////////////
    Not ! and && or ||
    #include
    using namespace std;

    int main()

    {
    freopen("mm.inp","r",stdin);
    freopen("mm.out","w",stdout);

    string s;
    getline(cin, s);

    int dem = 0;
    bool dang_trong_day_so = false;

    for (char c : s)
    {
    if (isdigit(c))
    {
    if (!dang_trong_day_so)
    {
    dem++;

    // bắt đầu dãy số mới

    dang_trong_day_so = true; // đánh dấu đang trong dãy
    }
    }
    else
    {
    dang_trong_day_so = false;
    }
    }

    // kết thúc dãy số

    cout << dem;
    return 0;
    }
    //////////////////////////////
    Tóm nhanh ý nghĩa thuật toán


    Đề ngầm hiểu: cho dãy gồm n số nguyên.



    Bài làm:
    o

    Đếm xem có bao nhiêu số khác nhau trong dãy (Q).

    o

    Tìm xem giá trị nào xuất hiện nhiều nhất, với tần suất bao nhiêu (P).



    Về kỹ thuật lập trình:



    Dùng unordered_map để đếm tần suất là rất chuẩn, rất hiện đại.



    Duyệt bằng for (auto &p : cnt) là cách viết ngắn, rõ, nên khuyến khích.

    #include
    using namespace std;
    int main() {
    // Đọc từ file mm.inp, ghi ra file mm.out
    freopen("mm.inp", "r", stdin);
    freopen("mm.out", "w", stdout);
    int n;
    cin >> n; // đọc số lượng phần tử
    unordered_map cnt; // cnt[x] = số lần xuất hiện của x
    for (int i = 0; i < n; i++) { // chú ý: < n, không phải <= n
    int x;
    cin >> x;
    cnt[x]++; // tăng số lần xuất hiện của giá trị x
    }

    int Q = cnt.size(); // số lượng giá trị khác nhau
    int P = 0;

    // tần suất lớn nhất

    // duyệt qua từng cặp (key, value) trong cnt
    for (auto &p : cnt) {
    P = max(P, p.second); // p.second là số lần xuất hiện của p.first
    }
    cout << Q << "\n" << P;
    return 0;
    }
    3. unordered_map cnt;




    Đây là một cấu trúc dữ liệu ánh xạ (bản đồ) từ:
    o

    key: int (giá trị x trong dãy)

    o

    value: int (số lần x xuất hiện)

    Ta dùng nó như một cái “từ điển”:
    o

    cnt[x] = số lần xuất hiện của giá trị x.



    Vì là unordered_map nên:



    Các phần tử không sắp xếp theo thứ tự tăng dần của key.



    Tra cứu / thêm / tăng đếm thường rất nhanh (trung bình O(1)).

    Ví dụ: nếu dãy là 5 7 5 5 9
    thì sau vòng lặp:


    cnt[5] = 3



    cnt[7] = 1



    cnt[9] = 1

    2.4. Vòng lặp nhập và đếm:
    for (int i = 0; i < n; i++) { int x; cin >> x; cnt[x]++; }


    for (int i = 0; i < n; i++): lặp đúng n lần, mỗi lần đọc 1 số.





    cnt[x]++:
    o

    Nếu x chưa từng xuất hiện → cnt[x] tự động khởi tạo = 0, rồi tăng lên 1.

    o

    Nếu x đã xuất hiện → tăng số lần thêm 1.

    Đây là mẫu rất chuẩn để đếm tần suất xuất hiện của các giá trị.

    2.5. int Q = cnt.size();


    cnt.size() trả về số lượng key khác nhau trong unordered_map.



    Nghĩa là: có bao nhiêu giá trị phân biệt trong dãy.



    Ví dụ:



    Dãy: 5 7 5 5 9



    Các key: 5, 7, 9 → cnt.size() = 3 → Q = 3.

    2.6. Duyệt unordered_map bằng for (auto &p : cnt)
    int P = 0; for (auto &p : cnt) P = max(P, p.second);


    auto &p:
    o

    p là một cặp pair đại diện cho một phần tử trong cnt.

    o

    p.first là key (giá trị x).

    o

    p.second là value (số lần x xuất hiện).

    o

    Dùng auto để đỡ phải viết kiểu dài dòng.



    P = max(P, p.second);



    p.second là tần suất của một giá trị nào đó.



    Lần lượt lấy giá trị lớn nhất trong tất cả các p.second.
    → Kết quả: P là số lần xuất hiện nhiều nhất của một giá trị trong dãy.

    Ví dụ dãy 5 7 5 5 9:


    cnt[5] = 3



    cnt[7] = 1



    cnt[9] = 1

    Duyệt:



    gặp 5 → P = max(0, 3) = 3



    gặp 7 → P = max(3, 1) = 3



    gặp 9 → P = max(3, 1) = 3

    Kết quả: P = 3.
    2.7. In kết quả
    cout << Q << "\n" << P;


    In:



    Dòng 1: số lượng giá trị khác nhau.



    Dòng 2: tần suất lớn nhất.

    Vì sao kỹ thuật này mạnh?
    1. Đếm nhanh như chớp (trung bình O(1) mỗi lần)
    unordered_map dùng bảng băm, nên mỗi lần tăng đếm:
    cnt[x]++;
    gần như tức thì, không cần tìm kiếm tuyến tính hay nhị phân.
    2. Không cần biết trước giá trị lớn hay nhỏ
    Dãy có thể chứa:


    số âm



    số lớn hàng triệu



    số lặp lại



    số không theo thứ tự

    → unordered_map vẫn xử lý gọn.
    3. Tự động tạo key mới khi gặp giá trị mới
    Không cần kiểm tra tồn tại:
    if (cnt.find(x) == cnt.end()) ...
    Chỉ cần:
    cnt[x]++;

    Nếu x chưa có → tự tạo với giá trị 0 rồi tăng lên 1.
    4. Duyệt tất cả giá trị phân biệt rất dễ
    for (auto &p : cnt) { // p.first = giá trị // p.second = số lần xuất hiện }
    Không cần mảng đánh dấu, không cần sort.
    5. Giải được rất nhiều dạng bài
    Kỹ thuật này xuất hiện trong:


    đếm số khác nhau



    tìm phần tử xuất hiện nhiều nhất



    tìm mode



    kiểm tra tần suất



    phát hiện phần tử xuất hiện 1 lần



    xử lý dãy lớn không thể sort



    bài toán mật mã, tần suất ký tự



    bài toán thống kê dữ liệu

    Nói cách khác: đây là một trong những kỹ thuật “đinh” của lập trình thi đấu và xử lý dữ liệu.
    Bạn dạy học sinh kỹ thuật này là cực kỳ tốt
    Nhiều em chỉ biết sort rồi đếm, hoặc dùng mảng đánh dấu, nhưng khi gặp:


    giá trị âm



    giá trị lớn



    giá trị không liên tục

    → là “tắc đường”.
    unordered_map giúp vượt qua tất cả những hạn chế đó.
    1. Google tìm ảnh gốc / ảnh tương tự hoạt động thế nào?
    Google không so sánh từng pixel (quá chậm).
    Thay vào đó, họ chuyển ảnh thành một tập các đặc trưng (features), ví dụ:


    màu sắc chủ đạo



    các cạnh (edges)



    các điểm đặc biệt (keypoints)



    mô tả đặc trưng (descriptors) như SIFT, SURF, ORB



    fingerprint của ảnh

    Sau đó họ đếm tần suất hoặc so khớp tần suất của các đặc trưng này.
    Ví dụ:


    Ảnh A có 120 điểm đặc trưng loại X



    Ảnh B có 118 điểm đặc trưng loại X
    → Khả năng cao là ảnh giống nhau.

    Đây chính là dạng đếm tần suất đặc trưng.
    2. Liên hệ với unordered_map trong bài toán của bạn
    Trong bài toán số nguyên:
    cnt[x]++;
    Bạn đang:


    chuyển dãy số thành bản đồ tần suất



    tìm xem có bao nhiêu giá trị khác nhau



    tìm xem giá trị nào xuất hiện nhiều nhất

    Trong xử lý ảnh:


    mỗi đặc trưng (feature) cũng được xem như một “giá trị”



    hệ thống đếm xem đặc trưng nào xuất hiện bao nhiêu lần



    so sánh tần suất giữa các ảnh

    Tư duy là giống nhau:
    → biến dữ liệu thô thành “bản đồ tần suất” để so sánh.
    3. Vì sao kỹ thuật này mạnh?
    Vì nó:


    nhanh (O(1) trung bình)



    không cần sắp xếp



    không cần biết trước giá trị



    xử lý được dữ liệu lớn



    dễ mở rộng sang nhiều dạng dữ liệu khác nhau

    Đó là lý do tại sao:


    Google dùng hashing + tần suất để tìm ảnh tương tự



    Các thuật toán NLP dùng hashing để đếm từ



    Các hệ thống chống spam dùng hashing để phát hiện mẫu lặp



    Các hệ thống phát hiện đạo văn dùng fingerprint + tần suất



    Các thuật toán big data dùng hashing để phân loại dữ liệu

    Và trong lập trình thi đấu, unordered_map là “vũ khí” cực mạnh.
    4. Tóm lại
    Bạn nhận xét rất tinh:
    “Có vẻ giống kỹ thuật search tìm ảnh gốc hay tương tự trong Google.”
    Đúng — tư duy nền tảng là giống nhau:
    → chuyển dữ liệu thành bản đồ tần suất để so sánh, phân loại, nhận dạng.
    Chỉ khác là:


    Google làm với đặc trưng ảnh



    Bạn đang làm với số nguyên

    /////////////////
    Bài 4 vườn cây
    (Tính chu vi từng vùng)
    #include
    using namespace std;
    int a[105][105];
    int m, n;

    int ans = 0;
    // 4 hướng: lên – phải – xuống – trái
    int dx[] = {-1, 0, 1, 0};
    int dy[] = {0, 1, 0, -1};
    bool insize(int u, int v) {
    return (u >= 1 && u <= m && v >= 1 && v <= n);
    }
    int main() {
    freopen("vuoncay.inp", "r", stdin);
    freopen("vuoncay.out", "w", stdout);
    cin >> m >> n;
    // đọc ma trận cây
    for (int i = 1; i <= m; i++)
    for (int j = 1; j <= n; j++)
    cin >> a[i][j];
    // duyệt từng ô
    for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
    // duyệt 4 hướng
    for (int k = 0; k < 4; k++) {
    int u = i + dx[k];
    int v = j + dy[k];
    // nếu ô bên cạnh hợp lệ
    if (insize(u, v)) {
    // nếu khác loại → cần dây
    if (a[i][j] != a[u][v]) {

    ans++;
    }
    }
    }
    }
    }
    cout << ans;
    return 0;
    }
    Ý tưởng thật sự của bài này là gì?
    Dựa vào cấu trúc:


    Có ma trận a[m][n]



    Có 4 hướng tx, ty



    Có hàm backtrack(u, v)



    Có đánh dấu a[u][v] = -1



    Có đếm ans++ khi gặp ô khác loại

    → Đây là thuật toán duyệt vùng (region flood-fill) để:
    Đếm số cạnh biên giữa một vùng cây và các vùng cây khác.
    Nói cách khác:


    Mỗi vùng cây cùng loại là một “khu vực”.



    Khi duyệt một ô, nếu cạnh bên cạnh nó là cây khác loại → tăng ans.



    Sau đó lan sang các ô cùng loại để duyệt tiếp.

    Đây là dạng bài đếm chu vi của một vùng, hoặc đếm số tiếp xúc giữa các vùng khác nhau.
    Không liên quan đến “độ dài dây giữa hai cây kề nhau cùng loại”.
    2. Phân tích chi tiết từng đoạn code
    2.1. Mảng hướng

    int tx[] = {-1, 0, 1, 0}; int ty[] = {0, 1, 0, -1};
    → Duyệt 4 hướng: lên – phải – xuống – trái.
    2.2. Hàm kiểm tra trong biên
    bool insize(int u, int v) { return (1 <= u && u <= m && 1 <= v && v <= n); }
    → Kiểm tra ô (u, v) có nằm trong ma trận không.
    2.3. Hàm backtrack (thực chất là DFS)
    (1) Đếm biên tiếp xúc với cây khác loại
    for (int k = 0; k < 4; k++) { int u1 = u + tx[k], v1 = v + ty[k]; if (a[u1][v1] != -1 && a[u1][v1] !=
    a[u][v]) ans++; }
    Ý nghĩa:


    Nếu ô bên cạnh khác loại → tăng ans.

    Đây là đếm số cạnh biên giữa vùng này và vùng khác.
    (2) Đánh dấu đã thăm
    a[u][v] = -1;
    → Đánh dấu ô này đã duyệt.
    (3) Lan sang các ô cùng loại
    for (int k = 0; k < 4; k++) { int u1 = u + tx[k], v1 = v + ty[k]; if (insize(u1, v1) && a[u1][v1] != -1 &&
    a[u1][v1] == a[u][v]) backtrack(u1, v1); }
    → Nếu ô bên cạnh cùng loại → tiếp tục DFS.
    ////////////////
    Duyệt ma trận vườn cây, chỉ xét 2 hướng (phải và xuống).
    Nếu hai cây kề nhau khác loại → cần 1 đoạn dây.
    Nếu cùng loại → không cần dây.
    Ma trận phải kín, không có ô trống.
    #include
    using namespace std;
    int main() {

    freopen("vuoncay.inp", "r", stdin);
    freopen("vuoncay.out", "w", stdout);
    int m, n;
    cin >> m >> n;
    vector> a(m + 1, vector(n + 1));
    for (int i = 1; i <= m; i++)
    for (int j = 1; j <= n; j++)
    cin >> a[i][j];
    long long ans = 0;
    // Duyệt từng ô
    for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
    // Xét cạnh phải
    if (j + 1 <= n && a[i][j] != a[i][j + 1])
    ans++;
    // Xét cạnh dưới
    if (i + 1 <= m && a[i][j] != a[i + 1][j])
    ans++;
    }
    }
    // Cộng thêm 4 cạnh ngoài
    ans += 2 * m + 2 * n;
    cout << ans;
    return 0;
    }
    //////////////////

    vector(n + 1)
    → tạo một hàng có n + 1 phần tử, tất cả = 0.
    (2) vector a(m + 1, ...)
    → tạo m + 1 bản sao của hàng trên
    → tức là tạo ma trận (m+1) × (n+1).
    1. +1 dùng để tạo mảng từ chỉ số 1 thay vì 0
    Trong C++:


    Mảng bình thường bắt đầu từ 0



    Nhưng khi làm bài toán ma trận, rất nhiều người thích dùng chỉ số 1..m và 1..n cho dễ
    đọc, dễ hiểu.

    Ba dạng bài thường gặp trong các bài “vườn cây”
    1) Dạng A – Tính tổng dây phân cách giữa các cây khác loại
    ✔ Duyệt phải + xuống
    ✔ Không cần DFS
    ✔ Không đếm trùng
    ✔ Ma trận phải kín
    ✔ Công thức đơn giản, dễ hiểu
    → Đây chính là cách bạn đang dùng, và nó rất gọn.
    2) Dạng B – Tính chu vi của từng vùng cây
    ✔ Cần DFS/BFS
    ✔ Phải đánh dấu đã thăm
    ✔ Mỗi cạnh tiếp xúc với vùng khác hoặc biên ngoài tính 1 lần
    ✔ Không cần ma trận kín
    → Đây là dạng “chu vi vùng” kinh điển.
    3) Dạng C – Tính tổng dây bao quanh toàn bộ vườn
    ✔ Lấy kết quả dạng A
    ✔ Cộng thêm chu vi ngoài: 2m + 2n
    → Đây là dạng “rào vườn”.

    Vì sao bạn cảm thấy có thể còn cách khác?
    Vì tùy đề bài:


    Nếu đề nói “tính dây phân cách” → dùng dạng A



    Nếu đề nói “tính chu vi vùng” → dùng dạng B



    Nếu đề nói “tính dây bao quanh vườn” → dùng dạng C



    Nếu đề nói “tính số vùng cây” → lại là một dạng khác (đếm connected components)

    Nhiều học sinh hay nhầm lẫn giữa các dạng này, nên code của các em thường “rối”, “lệch đề”,
    hoặc “dùng DFS không cần thiết”.
    Bài tham khảo:
    #include
    using namespace std;
    int a[105][105];
    int m, n;
    int ans = 0;
    // 4 hướng: lên – phải – xuống – trái
    int dx[] = {-1, 0, 1, 0};
    int dy[] = {0, 1, 0, -1};
    bool insize(int u, int v) {
    return (u >= 1 && u <= m && v >= 1 && v <= n);
    }
    int main() {
    freopen("vuoncay.inp", "r", stdin);
    freopen("vuoncay.out", "w", stdout);
    cin >> m >> n;
    // đọc ma trận cây
    for (int i = 1; i <= m; i++)

    for (int j = 1; j <= n; j++)
    cin >> a[i][j];
    // duyệt từng ô
    for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
    // duyệt 4 hướng
    for (int k = 0; k < 4; k++) {
    int u = i + dx[k];
    int v = j + dy[k];
    // nếu ô bên cạnh hợp lệ
    if (insize(u, v)) {
    // nếu khác loại → cần dây
    if (a[i][j] != a[u][v]) {
    ans++;
    }
    }
    }
    }
    }
    cout << ans;
    return 0;
    }
    /////////////////
    1. Ý tưởng bài toán (chuẩn)
    Bạn có n vị trí trạm thu sóng trên một trục số (1 chiều).
    Bạn muốn đặt k trạm phát sóng, mỗi trạm có bán kính phủ sóng = x.

    Một trạm phát sóng đặt tại vị trí p sẽ phủ được đoạn:
    [p-x,\; p+x]
    Mục tiêu:
    Tìm giá trị nhỏ nhất của x sao cho chỉ cần ≤ k trạm phát sóng là phủ hết n vị trí.
    Đây là bài toán kinh điển:


    Sắp xếp vị trí



    Dùng tìm kiếm nhị phân trên đáp án x



    Với mỗi x → kiểm tra xem cần bao nhiêu trạm phát sóng



    Nếu ≤ k → thử giảm x



    Nếu > k → phải tăng x

    2. Thuật toán kiểm tra (hàm kt(x))
    Đây là phần quan trọng nhất.
    Với một giá trị x cho trước:
    1. Đặt trạm phát sóng đầu tiên tại vị trí phủ được điểm đầu tiên a[1]
    2. Trạm này phủ được đến a[1] + 2x
    3. Tìm điểm đầu tiên nằm ngoài vùng phủ → đặt trạm thứ
     
    Gửi ý kiến

    Luyện thiết kế Web


    I LOVE YOU

    Thiết kế
    Chủ đề web
    Email của bạn
    *Nội dung*
    Đường dẫn Tựa đề
    Thêm vào



    Đây là đoạn mã nguồn trang web của bạn. Hãy tìm một Domain+Host để đưa nó lên mạng



    BẠN CHỈ CẦN COPPY MỘT ĐOẠN CODE DÁN VÀO VÀ ẤN VÀO LÀM XONG VÀ ẤN VÀO XEM THỬ LÀ CÓ KẾT QUẢ NGAY

    Vui 2022 Trường Thiên Thư

    Ảnh trực tuyến 3D