• SQRTOJ
  • Trang chủ
  • Bài
  • Các bài nộp
  • Thành viên
  • Các kỳ thi
  • Thông tin
    >
    • Máy chấm
    • Custom Checkers
    • Github
  • Fanpage
  • Group
  • shop
VI EN Đăng nhập  hoặc  Đăng ký
  • Blog
  • Sự kiện
  • Tin tức
  • Blog

0

alo Vũ ơi Vũ

ngquang03 đã đăng vào 25, Tháng 3, 2026, 8:39

nà ná na na anh Phùng Thanh Độ anh Độ mixi, Phùng Thanh Độ anh Độ mixi, anh Độ cảm ơn anh Độ

ngquang03
o25, Tháng 3, 2026, 8:39 0

4

Thi thử HSG THCS - Xuân Bính Ngọ 2026

Sunlight đã đăng vào 7, Tháng 2, 2026, 4:49

Xin chào các bạn,

Thời điểm năm hết Tết đến cũng là lúc mà các contest thi thử HSG9 của câu lạc bộ SQRT quay trở lại. Với xuân Bính Ngọ năm nay, chúng mình mời các bạn tham gia chuỗi 4 contest của chúng mình để có thể nhận được những phong bao lì xì cực kỳ hấp dẫn nhân dịp đầu năm mới nhé.

Các bạn hãy cùng chúng mình điểm qua những thông tin chính của chuỗi contest năm nay nào :D

Thời gian diễn ra contest

Chuỗi contest sẽ kéo dài trong 4 ngày, từ ngày 10 đến 13 tháng 2 năm 2026. Contest của mỗi ngày sẽ bắt đầu vào 20 giờ và kết thúc vào 22 giờ 30.

  • Ngày thi thứ nhất: 20h00 - 22h30 ngày 10 tháng 2 năm 2026, diễn ra tại đây.
  • Ngày thi thứ hai: 20h00 - 22h30 ngày 11 tháng 2 năm 2026, diễn ra tại đây.
  • Ngày thi thứ ba: 20h00 - 22h30 ngày 12 tháng 2 năm 2026, diễn ra tại đây.
  • Ngày thi thứ tư: 20h00 - 22h30 ngày 13 tháng 2 năm 2026, diễn ra tại đây.

Kết quả sẽ được công bố vào tối ngày 14 tháng 2 năm 2026 trên server Discord SQRT Community.

Nộp bài và chấm điểm

  • Đề bài của mỗi ngày thi bao gồm nhiều bài tập. Với mỗi bài tập, thí sinh được nộp bài không giới hạn số lần.
  • Kết quả được ghi nhận là kết quả của lần nộp cuối cùng không bị Lỗi biên dịch (Compile Error).
  • Trong thời gian thi, bài nộp của thí sinh chỉ được chấm với test ví dụ. Sau khi kỳ thi kết thúc, các bài nộp sẽ được chấm lại với test chính thức.
  • Đề thi gồm từ 3 đến 5 bài, có tổng điểm tối đa là 20 điểm. Mỗi bài tập có nhiều test chấm. Điểm của mỗi bài tính theo từng test mà thí sinh đưa ra kết quả đúng.

Điểm chung cuộc và xét giải

  • Điểm chung cuộc của mỗi thí sinh được tính bằng tổng điểm của 4 vòng thi mà thí sinh tham gia, trong đó điểm của vòng thi có điểm số cao nhất sẽ được nhân đôi. (Ví dụ, nếu một thí sinh đạt được số điểm ở các vòng thi lần lượt là 11, 12, 15, 13 thì điểm chung cuộc của thí sinh đó là ~11 + 12 + 15 \times 2 + 13 = 67~ điểm).
  • Thí sinh được xét giải như sau:
    • Giải Nhất: không quá ~2\%~ số thí sinh tham dự kỳ thi, trị giá ~50.650~ VNĐ mỗi giải.
    • Giải Nhì: không quá ~4\%~ số thí sinh tham dự kỳ thi, trị giá ~30.390~ VNĐ mỗi giải.
    • Giải Ba: không quá ~6\%~ số thí sinh tham dự kỳ thi, trị giá ~20.260~ VNĐ mỗi giải.
    • Giải Khuyến khích: không quá ~8\%~ số thí sinh tham dự kỳ thi, trị giá ~10.130~ VNĐ mỗi giải.
  • Các giải phụ:
    • Lucky AC (tính cho mỗi bài nộp nhận kết quả Accepted): ~10~ giải, mỗi giải trị giá ~5.065~ VNĐ.
    • Lucky Solution (tính cho mỗi bài nộp nhận điểm lớn hơn ~0~): ~10~ giải, mỗi giải trị giá ~5.065~ VNĐ.
    • Các submission có mã số đẹp:
      • Mã số chia hết cho ~1000~ (57000, 58000, ...): ~5.065~ VNĐ mỗi giải.
      • Mã số chia hết cho ~10000~ (60000, ...): ~20.260~ VNĐ mỗi giải.
      • Mã số chỉ gồm các chữ số ~6, 9, 4, 2, 0~: ~5.065~ VNĐ mỗi giải, ~20.260~ nếu mã số gồm tất cả các chữ số trên.

(Một thí sinh được tính là dự thi nếu tham gia ít nhất một trong bốn contest và có điểm chung cuộc lớn hơn 0. Với các giải Lucky AC và Lucky Solution, nếu thí sinh nộp bài nhiều lần cho một bài tập thì chỉ tính lần nộp cuối cùng)

Minigame

Dự đoán số lượng thí sinh dự thi: Các bạn comment dưới bài viết này tổng số thí sinh dự thi theo dự đoán của các bạn. Bạn có kết quả tốt nhất (chênh lệch so với số lượng thực tế thấp nhất) sẽ nhận được giải thưởng ~20.260~ VNĐ.

Sunlight
o7, Tháng 2, 2026, 4:49 9

2

cau hoi

vbquan_0612 đã đăng vào 22, Tháng 11, 2025, 9:22

tris ko gay dung khong

vbquan_0612
o22, Tháng 11, 2025, 9:22 0

4

[CÔNG BỐ KỲ THI QHHOJ X SQRT CUP 2025]

Sunlight đã đăng vào 30, Tháng 6, 2025, 9:15

Xin chào các bạn,

Nếu các bạn từng theo dõi TLEOJ và câu lạc bộ TLE, chắc hẳn các bạn đã biết về TLEOJ Cup, giải đấu thường niên được tổ chức bởi Câu lạc bộ TLE. Năm nay, chúng mình, câu lạc bộ SQRT, cùng với sự hỗ trợ của câu lạc bộ Tin học trường THPT Chuyên Quốc học Huế mang đến cho các bạn một giải đấu mới mang tên QHHOJ x SQRT Cup 2025, hứa hẹn sẽ là một sân chơi chất lượng và bổ ích cho các bạn học sinh đam mê Lập trình thi đấu.

QHHOJ x SQRT Cup 2025 bao gồm 3 vòng thi:

  • Vòng loại thứ nhất: 20 giờ 00, ngày 05 tháng 7 năm 2025.
  • Vòng loại thứ hai: 20 giờ 00, ngày 19 tháng 7 năm 2025.
  • Vòng chung kết: dự kiến tổ chức vào ngày 11 và 12 tháng 8 năm 2025. Đặc biệt trong năm nay, vòng chung kết dự kiến sẽ được tổ chức trực tiếp tại trường THPT Chuyên Quốc học Huế.
  • Năm nay, giải đấu sẽ được chia làm 2 bảng, mỗi bảng chọn 15 thí sinh có thành tích xuất sắc nhất tham dự vòng chung kết:
  • Bảng Không chuyên: dành cho học sinh THCS và THPT không chuyên Tin (tính đến hết năm học 2024 - 2025).
  • Bảng Chuyên: dành cho học sinh THPT chuyên Tin và sinh viên Đại học. Còn chần chờ gì nữa, đăng ký ngay thôi nào!!!

Link đăng ký:

  • Bảng Chuyên: thi trên hệ thống SQRTOJ:
    • Vòng loại thứ nhất: https://sqrtoj.edu.vn/contest/028
    • Vòng loại thứ hai: https://sqrtoj.edu.vn/contest/029
  • Bảng Không chuyên: thi trên hệ thống QHHOJ:
    • Vòng loại thứ nhất: https://qhhoj.com/contest/sqrtcup25r1
    • Vòng loại thứ hai: https://qhhoj.com/contest/sqrtcup25r2

Thể lệ cuộc thi: https://drive.google.com/file/d/1UWPIX6Rii7aiJNLIqTvFr913j33Ydj1R/view?usp=sharing

Ngoài ra, Ban tổ chức cũng sẽ đăng các tài liệu / nội dung khác lên đây: https://drive.google.com/drive/folders/1nNQziKwG8OH6DWzAZTRd82SVLd6P8K4L?usp=drive_link

Sunlight
o30, Tháng 6, 2025, 9:15 0

2

Câu lạc bộ SQRT

Sunlight đã đăng vào 22, Tháng 6, 2025, 17:03

Xin chào các bạn, chúng mình là câu lạc bộ SQRT.

SQRT được thành lập vào tháng 8 năm 2024 bởi một số bạn học sinh trên cả nước, với tiền thân là câu lạc bộ TLE. Đây là một câu lạc bộ phi lợi nhuận, được lập ra với mục đích chia sẻ kiến thức và phát triển cộng đồng Tin học Việt Nam. Chắc hẳn những bạn biết đến câu lạc bộ SQRT đều biết đến hệ thống chấm bài online của chúng mình – SQRT Online Judge (https://sqrtoj.edu.vn/), dự án chính của câu lạc bộ SQRT. Tuy nhiên, chúng mình vẫn có những nội dung khác mà các bạn có thể tìm thấy tại:

  • Fanpage: SQRT Online Judge (https://www.facebook.com/sqrtoj).
  • Discord: SQRT Community (https://discord.gg/R2hhGGDQvf) – đổi tên từ server Discord của câu lạc bộ TLE, hiện có hơn 1100 thành viên.
  • Facebook group: SQRT Online Judge – SQRTOJ (https://www.facebook.com/groups/1379271806818201) – đang phát triển.

Nếu các bạn là giáo viên dạy Tin học và muốn có một nền tảng chấm bài của riêng mình, các bạn có thể tham khảo SQRT Shop (https://sqrtoj.edu.vn/shop/). Chúng mình có cung cấp các dịch vụ như sử dụng tính năng tổ chức và cài đặt – duy trì một trang chấm bài riêng với giá cả phải chăng. Nếu các bạn muốn ủng hộ câu lạc bộ, hãy đón chờ những sản phẩm như áo, áo khoác SQRT trong tương lai. Nếu các bạn có ý kiến đóng góp cho câu lạc bộ, hãy comment dưới bài viết này, inbox fanpage hoặc nhắn trong server Discord và ping @sqrt.sunlight để được giải đáp.

Chúng mình xin chân thành cảm ơn sự ủng hộ của các bạn trong gần một năm qua. Hy vọng các bạn có thể tiếp tục đi cùng chúng mình trong thời gian tới.

Sunlight
o22, Tháng 6, 2025, 17:03 0

2

Tổng hợp đề thi tuyển sinh lớp 10 chuyên Tin trên SQRTOJ

Sunlight đã đăng vào 4, Tháng 6, 2025, 15:13

Xin chào các bạn,

Hiện nay rất nhiều tỉnh, thành phố, trường THPT chuyên đã tổ chức các kỳ thi tuyển sinh lớp 10, trong đó bao gồm cả môn chuyên Tin. Để giúp các bạn có thể luyện tập, câu lạc bộ SQRT đã đăng một số đề thi tuyển sinh lớp 10 chuyên Tin mới thi lên hệ thống, bao gồm các đề của các tỉnh / thành phố / trường sau:

  • Vĩnh Phúc: mã contest edu003.
  • Đà Nẵng: mã contest edu004.
  • Lâm Đồng: mã contest edu005.
  • Hà Tĩnh: mã contest edu006.
  • Quảng Nam: mã contest edu007.
  • Quảng Bình: mã contest edu008.
  • Bắc Giang: mã contest edu009.
  • Phú Thọ: mã contest edu010.
  • Quảng Ngãi: mã contest edu011.
  • Đắk Nông: mã contest edu012.
  • Tiền Giang: mã contest edu013.
  • Đắk Lắk: mã contest edu014.
  • Nghệ An: mã contest edu015.
  • Thái Nguyên: mã contest edu016.

Post này sẽ được cập nhật liên tục khi có đề thi mới.

Chúc các bạn luyện tập vui vẻ.

Sunlight
o4, Tháng 6, 2025, 15:13 0

3

Tổng kết Kỳ thi thử Giao lưu Tin học trẻ

clue_ đã đăng vào 16, Tháng 4, 2025, 16:26

Vậy là contest thi thử giao lưu Tin học trẻ của CLB SQRT chúng mình đã diễn ra.

Tiện đây, mình xin gửi lời chúc mừng đến các bạn sau đã đạt top ~20~ của contest ở cả hai bảng và nhận được các phần thưởng giảm giá khi thi chính thức từ chúng mình:

  • ~2~ giải Nhất mỗi bảng (thứ hạng ~1~ đến ~2~), mỗi giải giảm ~50.000~ đồng lệ phí thi chính thức.
  • ~3~ giải Nhì mỗi bảng (thứ hạng ~3~ đến ~5~), mỗi giải giảm ~30.000~ đồng lệ phí thi chính thức.
  • ~6~ giải Ba mỗi bảng (thứ hạng ~6~ đến ~11~), mỗi giải giảm ~20.000~ đồng lệ phí thi chính thức.
  • ~9~ giải Khuyến khích mỗi bảng (thứ hạng ~12~ đến ~20~), mỗi giải giảm ~10.000~ đồng lệ phí thi chính thức.

Các bạn có thể đăng ký thi chính thức tại đây.

Bảng THCS:

Thứ hạng Handle Tổng điểm
2 NNCS 400
2 phucdiz 400
3 fasadah 360
4 Siquy 339
5 Suisei 313
7 mastercheat 306
7 Interpol 306
9 thienan 260
9 linhhnt2022 260
10 NguyenTrungPhuc 201
20 fishsauce_05 200
20 ngotungvp 200
20 havyyyyy 200
20 vuthilinh0487 200
20 Tranquility 200
20 toandinhngoc 200
20 duonglaptrinh 200
20 sWeaterVN 200
20 dienkhungak47 200
20 pt1410 200

Bảng THPT:

Thứ hạng Handle Tổng điểm
1 amongusus 400
4 tuananhtank2708 360
4 imlaggs 360
4 Cody 360
6 27augain 340
6 nai0610 340
7 treasure 330
8 khoad 328
9 Bao987 312
10 ninh 300
11 phattpro2008 294
12 killuacoconut 206
15 dangthh 200
15 vnedu 200
15 nguyenvanphu2 200
16 thaibeo123 172
19 vudinhlong 160
19 khongbietnoigi 160
19 longyu3657 160
20 nguyenthucuteluve 140

Lời giải của bài tập đã được public, các bạn có thể luyện tập tại đây.

Lời cuối, cảm ơn các bạn đã tham gia contest của chúng mình!

clue_
o16, Tháng 4, 2025, 16:26 0

6

Giao lưu tin học trẻ 2025

Peace đã đăng vào 12, Tháng 4, 2025, 14:30

boom.webp

Xìn chào tất cả các bạn, vẫn là mình - Peace đây!


Kỳ thi Giao Lưu Tin Học Trẻ 2025 chính thức được khởi động, mang đến sân chơi bổ ích để các bạn trau dồi kỹ năng, chuẩn bị hành trang vững vàng cho kỳ thi chính thức. Dù bạn là học sinh Tiểu học, THCS hay THPT, đây đều là cơ hội để thể hiện tài năng và giao lưu học hỏi!


📅 THÔNG TIN CHI TIẾT
1. Thời gian thi
  • Thi thử:
    ⏰ 20:00 – 23:00, Chủ Nhật, ngày 13/04/2025
    🆓 Miễn phí tham gia, làm quen với hệ thống và format đề.
  • Thi chính thức:
    ⏰ 20:00 – 23:00 các tối Thứ 6, Thứ 7, Chủ Nhật
    📅 Từ ngày ~18~/~04~/~2025~ đến ~04~/~05~/~2025~ (~3~ tuần, ~9~ ngày thi).
2. Đối tượng tham gia
  • Bảng A: Học sinh Tiểu học và THCS.
  • Bảng B: Học sinh THPT hoặc thí sinh đã đạt giải Nhất Bảng A ở các tuần trước.
3. Giải thưởng
  • Thi thử:
    🏅 Giảm ~10.000~ VNĐ – ~50.000~ VNĐ lệ phí thi chính thức (Tổng giá trị ~800,000~ VNĐ).
  • Thi chính thức:
    🥇 Giải Nhất: ~50.000~ VNĐ.
    🥈 Giải Nhì: ~40.000~ VNĐ.
    🥉 Giải Ba: ~30.000~ VNĐ.
    🎖️ Khuyến khích ~20.000~ VNĐ.
4. Lệ phí
  • Thi thử: Miễn phí.
  • Thi chính thức: ~20.000~ VNĐ/thí sinh/tuần (Đóng một lần cho cả ~3~ tuần).

📝 QUY ĐỊNH THI
  • Đề thi: 4 bài/bảng, độ khó phân loại từ cơ bản đến nâng cao, biên soạn bởi đội ngũ chuyên môn SQRT.
  • Thời gian làm bài:
    • Bảng A: ~150~ phút.
    • Bảng B: ~180~ phút.
  • Hệ thống chấm:
    ✅ Nộp bài không giới hạn, lấy điểm cao nhất.
    ✅ Xem điểm ngay trong thời gian thi.

🔗 CÁCH THỨC ĐĂNG KÝ
  • Thi thử: Đăng ký trực tiếp trên hệ thống SQRTOJ:
    → Bảng THCS | Bảng THPT
  • Thi chính thức: Đăng ký qua Google Form:
    → Google Form

💬 LỜI NHẮN TỪ BAN TỔ CHỨC

Chúng tôi mong muốn tạo ra một môi trường thi đấu công bằng, lành mạnh và bổ ích cho tất cả thí sinh. Mọi thắc mắc hoặc cần hỗ trợ, vui lòng liên hệ qua:

  • Fanpage: SQRT
  • Discord: SQRT Community

Hãy đăng ký ngay để không bỏ lỡ cơ hội thể hiện bản thân và rinh về những phần thưởng giá trị! 🌟

👉 CLICK ĐĂNG KÝ THI CHÍNH THỨC

Peace
o12, Tháng 4, 2025, 14:30 0

0

Bài toán Kết nối động (Dynamic connectivity)

Sunlight đã đăng vào 29, Tháng 3, 2025, 6:39

1. Giới thiệu bài toán

Bài toán được phát biểu chi tiết tại VNOI - sqrt2_a. Tóm tắt như sau:

Cho một đồ thị vô hướng gồm ~n~ đỉnh và ~m~ cạnh ban đầu. Sau đó có ~k~ truy vấn, mỗi truy vấn thuộc một trong hai loại:

  1. Thêm cạnh ~(a, b)~.
  2. Xóa cạnh ~(a, b)~ (dữ liệu đảm bảo cạnh đang tồn tại).

Nhiệm vụ: sau mỗi truy vấn (và cả trước khi xử lý truy vấn nào), hãy in ra số thành phần liên thông của đồ thị.

Ràng buộc: ~(2 \le n \le 10^5), (1 \le m, k \le 10^5)~

Tổng số thao tác ~q = m + k~ cũng có thể lên tới ~2 \times 10^5~.

Đây là bài toán đồ thị động (dynamic graph) với cả hai thao tác thêm và xóa cạnh. Do kích thước lớn, cần những phương pháp tối ưu.


2. Các hướng tiếp cận

2.1. Hướng tiếp cận ngây thơ – BFS/DFS sau mỗi truy vấn

Ý tưởng:
Mỗi khi có thay đổi (thêm hoặc xóa cạnh), ta duyệt lại toàn bộ đồ thị bằng BFS hoặc DFS để đếm số thành phần liên thông.

Độ phức tạp:
Mỗi lần duyệt mất ~O(n + m')~ với ~m'~ là số cạnh hiện tại. Có ~q~ truy vấn, tổng độ phức tạp ~O(q \cdot (n + q))~ trong trường hợp xấu nhất.
Với ~n, q \le 2\times 10^5~, cách này quá chậm, không khả thi.

2.2. Hướng tiếp cận DSU cho trường hợp không có xóa

Nếu chỉ có thêm cạnh (đồ thị chỉ tăng dần), ta dùng Disjoint Set Union (DSU) rất hiệu quả:
Mỗi lần thêm cạnh, nếu hai đỉnh chưa cùng thành phần, ta hợp nhất và giảm số thành phần đi 1.
Độ phức tạp gần như ~O(1)~ cho mỗi thao tác.

Nhưng khi có xóa, DSU cơ bản không hỗ trợ tách rời các thành phần. Điều này đòi hỏi các kỹ thuật nâng cao hơn.

2.3. Hướng tiếp cận ~O(q \sqrt{q})~ – Chia nhóm truy vấn

Nguồn tham khảo: Editorial chính thức của đề bài

Phương pháp này dựa trên ý tưởng phân hoạch dãy thao tác thành các khối (block) có kích thước xấp xỉ ~\sqrt{q}~. Mỗi khối được xử lý độc lập, trong đó tận dụng tính chất: số lượng cạnh thay đổi trạng thái (thêm/xóa) trong một khối là nhỏ, từ đó giảm chi phí xử lý mỗi truy vấn.

2.3.1. Ý tưởng cốt lõi

Chia ~q~ thao tác (gồm ~m~ cạnh ban đầu và ~k~ truy vấn) thành các khối, mỗi khối có ~B = \lceil \sqrt{q} \rceil~ thao tác (có thể điều chỉnh ~B~ để tối ưu thực tế). Khi xét một khối cụ thể, ta phân các cạnh của đồ thị thành hai loại:

  • Cạnh tĩnh (static): những cạnh có mặt trong toàn bộ khối, nghĩa là chúng được thêm trước khối và không bị xóa trong khối (hoặc nếu có xóa thì sau khối). Các cạnh này không thay đổi trạng thái trong suốt khối.
  • Cạnh động (dynamic): những cạnh có ít nhất một thao tác thêm hoặc xóa nằm trong khối. Số lượng cạnh động trong một khối là ~O(B)~, vì mỗi thao tác trong khối chỉ ảnh hưởng đến một cạnh.

Xử lý một khối gồm ba bước chính:

  1. Xây dựng cấu trúc DSU cơ sở chỉ gồm các cạnh tĩnh (có thể dùng path compression để tăng tốc).
  2. Với mỗi truy vấn trong khối (theo thứ tự thời gian), ta cập nhật trạng thái của cạnh động tương ứng (thêm hoặc xóa), sau đó thêm tất cả các cạnh động đang tồn tại vào DSU cơ sở (chỉ thêm những cạnh chưa có) và ghi nhận số thành phần liên thông.
  3. Sau mỗi truy vấn, ta quay lui (rollback) các thao tác thêm cạnh động vừa thực hiện để DSU trở về trạng thái chỉ có cạnh tĩnh, chuẩn bị cho truy vấn tiếp theo trong khối.
2.3.2. Chi tiết các bước xử lý một khối

Giả sử khối hiện tại bao gồm các thao tác có chỉ số từ ~L~ đến ~R~ (đánh số 1..~q~).

Bước 1: Xác định cạnh tĩnh và cạnh động

  • Duyệt tất cả các cạnh đã biết (từ dữ liệu đầu vào). Mỗi cạnh có một khoảng thời gian tồn tại ~[l, r)~ (được xác định từ các cặp thêm/xóa).
  • Cạnh được gọi là tĩnh trong khối nếu ~l \le L~ và ~r > R~ (tức nó có mặt trong toàn bộ khối).
  • Cạnh được gọi là động nếu nó có giao với khối (~l \le R~ và ~r \ge L~) nhưng không phải là tĩnh.
  • Lưu ý: một cạnh có thể không xuất hiện trong khối (khi ~r \le L~ hoặc ~l > R~) – ta bỏ qua.

Bước 2: Xây dựng DSU cơ sở cho khối

  • Khởi tạo một DSU có hỗ trợ rollback (sẽ dùng cho cạnh động).
  • Duyệt tất cả cạnh tĩnh và thực hiện hợp nhất (union) trên DSU này. Vì các cạnh tĩnh không thay đổi trong khối, ta chỉ cần thực hiện một lần. Có thể dùng path compression để tăng tốc (nhưng phải cẩn thận vì DSU sẽ cần rollback cho cạnh động – thông thường ta dùng hai chế độ: một DSU riêng cho cạnh tĩnh với nén đường, và DSU động không nén để rollback; hoặc dùng chung một DSU nhưng chỉ nén khi thêm cạnh tĩnh và không nén khi thêm cạnh động). Để đơn giản, ta có thể dùng DSU với union by size và không nén đường cho toàn bộ, khi đó mỗi thao tác tốn ~O(\log n)~ – vẫn chấp nhận được.

Bước 3: Xử lý tuần tự từng truy vấn trong khối

Với mỗi truy vấn thứ ~i~ từ ~L~ đến ~R~:

  • Cập nhật trạng thái của cạnh liên quan đến truy vấn (nếu là thêm thì đánh dấu cạnh đó đang tồn tại, nếu xóa thì đánh dấu không tồn tại).
  • Lấy một snapshot (mốc) của DSU hiện tại (số lượng thay đổi đã lưu trong stack).
  • Duyệt qua tất cả cạnh động của khối: nếu cạnh đó đang tồn tại (trạng thái true), thực hiện hợp nhất hai đầu mút của nó trên DSU. Việc hợp nhất này phải được ghi lại để có thể rollback sau.
  • Số thành phần liên thông hiện tại của DSU chính là kết quả sau thao tác ~i~.
  • Rollback DSU về snapshot đã lưu (xóa tất cả các thay đổi vừa thực hiện do các cạnh động gây ra), đưa DSU về trạng thái chỉ chứa cạnh tĩnh.

Chú ý: Các cạnh động có thể có trạng thái thay đổi theo thời gian trong khối. Việc duyệt lại toàn bộ cạnh động mỗi lần truy vấn (thay vì chỉ cập nhật một cạnh) tuy tốn ~O(B)~ nhưng là chấp nhận được vì ~B~ nhỏ.

2.3.3. Kỹ thuật DSU có rollback

Để thực hiện rollback, DSU không được dùng path compression. Thay vào đó, ta chỉ dùng union by size (hoặc rank). Mỗi lần hợp nhất hai thành phần thành công, ta lưu lại các thông tin thay đổi vào một ngăn xếp (stack), ví dụ: đỉnh con bị đổi cha, kích thước cũ của gốc mới, và số thành phần liên thông trước khi hợp nhất. Khi cần rollback, ta lấy thông tin từ stack và khôi phục lại trạng thái cũ. Mỗi thao tác hợp nhất và rollback đều có chi phí ~O(1)~ (không tính chi phí tìm gốc – nhưng tìm gốc vẫn là ~O(\log n)~ do không nén).

Ngoài ra, để tăng tốc phần xử lý cạnh tĩnh, có thể tách riêng một DSU khác không cần rollback, sử dụng path compression. Khi đó trong mỗi truy vấn, ta sẽ kết hợp kết quả từ DSU tĩnh và các cạnh động tạm thời. Tuy nhiên, việc kết hợp này đòi hỏi phải ánh xạ các đỉnh về đại diện của DSU tĩnh, khá phức tạp. Trong khuôn khổ bài viết, ta giữ cách dùng chung một DSU rollback cho toàn bộ, vì độ phức tạp vẫn đạt yêu cầu.

2.3.4. Phân tích độ phức tạp
  • Số khối: ~\frac{q}{B} \approx \sqrt{q}~.
  • Chi phí xây dựng DSU cơ sở mỗi khối: duyệt qua tất cả cạnh để phân loại tĩnh/động và thực hiện union cho cạnh tĩnh. Mỗi cạnh được xét trong mỗi khối mà nó có giao với khối, và mỗi cạnh có thể là tĩnh trong nhiều khối liên tiếp. Tổng số lần xét cạnh trên tất cả các khối là ~O(q \sqrt{q})~ (vì mỗi cạnh với đoạn tồn tại độ dài ~d~ sẽ nằm trong ~O(d/B + 1)~ khối, tổng các ~d~ bằng ~O(q)~, suy ra tổng số lần xét là ~O(q \sqrt{q})~). Chi phí cho mỗi lần xét và union là ~O(\log n)~ (nếu không nén) hoặc ~O(\alpha(n))~ (nếu dùng nén riêng). Vậy tổng chi phí cho bước này là ~O(q \sqrt{q} \log n)~.
  • Chi phí xử lý truy vấn trong một khối: mỗi truy vấn phải duyệt qua tất cả cạnh động (số lượng ~O(B)~), thực hiện union (nếu cạnh đang tồn tại) và sau đó rollback. Mỗi union/rollback tốn ~O(\log n)~. Một khối có ~B~ truy vấn, do đó chi phí cho khối là ~O(B^2 \log n)~. Có ~\sqrt{q}~ khối, suy ra tổng chi phí cho bước này là ~O(q \sqrt{q} \log n)~.
  • Tổng độ phức tạp: ~O(q \sqrt{q} \log n)~.

Với ~q \le 2 \times 10^5~, ~\sqrt{q} \approx 447~, ~q \sqrt{q} \approx 9 \times 10^7~, nhân với ~\log n \approx 18~ cho khoảng 1,6 tỷ phép toán – lớn nhưng nếu cài đặt tối ưu (C++, dùng mảng tĩnh, tránh cấp phát động) vẫn có thể chạy trong 2-3 giây. Thực tế, hằng số nhỏ hơn do nhiều cạnh không cần xét ở mỗi khối. Nếu muốn an toàn hơn, có thể tăng kích thước khối lên một chút (ví dụ ~B = 700~) để giảm số khối, nhưng khi đó chi phí trong khối tăng lên. Cần cân nhắc.

2.3.5. Lưu ý khi cài đặt
  • Chọn kích thước khối: lý thuyết tối ưu là ~B = \sqrt{q}~, nhưng có thể thử ~B = 400~ hoặc ~500~ cho phù hợp với bộ nhớ đệm.
  • Xác định nhanh cạnh tĩnh/động: Nên tiền xử lý để biết với mỗi cạnh, khoảng thời gian tồn tại của nó dưới dạng ~[l, r)~ (giống như trong phương pháp segment tree). Sau đó, khi xét khối ~[L, R]~, chỉ cần so sánh ~l, r~ với ~L, R~.
  • Quản lý trạng thái hiện tại của cạnh động: Cần một mảng active[edge_id] để biết cạnh đó có đang tồn tại trong đồ thị tại thời điểm đang xét hay không. Mảng này được cập nhật liên tục qua các khối và qua các truy vấn trong khối.
  • DSU rollback: Cần lưu stack các thay đổi. Mỗi lần thêm cạnh động thành công, ta push một bản ghi (ví dụ: hai đỉnh, kích thước cũ, ...). Sau truy vấn, rollback đúng số lần đã push.
  • Khởi tạo lại DSU cho mỗi khối: Việc xây dựng lại DSU từ đầu cho mỗi khối (bằng cách thêm các cạnh tĩnh) tốn thời gian nhưng không quá lớn. Có thể cải tiến bằng cách chỉ cập nhật DSU giữa các khối nếu tập cạnh tĩnh thay đổi ít, nhưng điều này làm phức tạp code.
2.3.6. Code tham khảo
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>

using namespace std;

void setupIO() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
}

// Cấu trúc đại diện cho một cạnh (luôn đảm bảo u < v để dễ so sánh)
struct Edge {
    int u, v;
    bool operator<(const Edge& other) const {
        if (u != other.u) return u < other.u;
        return v < other.v;
    }
    bool operator==(const Edge& other) const {
        return u == other.u && v == other.v;
    }
};

struct Query {
    int t, id;
};

// Cấu trúc DSU hỗ trợ Rollback
struct DSU {
    int components;
    vector<int> parent;
    vector<int> sz;

    struct State {
        int u, v;
        int sz_u, sz_v;
        int components;
    };
    vector<State> history;

    DSU(int n) : components(n), parent(n + 1), sz(n + 1, 1) {
        iota(parent.begin(), parent.end(), 0);
    }

    void reset(int n_verts) {
        components = n_verts;
        for(int i = 1; i <= n_verts; ++i) {
            parent[i] = i;
            sz[i] = 1;
        }
        history.clear();
    }

    // Gộp tĩnh: Có Path Compression
    int find_pc(int i) {
        if (parent[i] == i) return i;
        return parent[i] = find_pc(parent[i]);
    }

    void unite_static(int i, int j) {
        int root_i = find_pc(i);
        int root_j = find_pc(j);
        if (root_i != root_j) {
            if (sz[root_i] < sz[root_j]) swap(root_i, root_j);
            parent[root_j] = root_i;
            sz[root_i] += sz[root_j];
            components--;
        }
    }

    // Gộp động: KHÔNG Path Compression để có thể Rollback
    int find_no_pc(int i) {
        while (parent[i] != i) i = parent[i];
        return i;
    }

    void unite_dynamic(int i, int j) {
        int root_i = find_no_pc(i);
        int root_j = find_no_pc(j);

        if (root_i == root_j) {
            history.push_back({-1, -1, -1, -1, components});
            return;
        }

        if (sz[root_i] < sz[root_j]) swap(root_i, root_j);
        history.push_back({root_i, root_j, sz[root_i], sz[root_j], components});

        parent[root_j] = root_i;
        sz[root_i] += sz[root_j];
        components--;
    }

    void rollback() {
        if (history.empty()) return;
        State st = history.back();
        history.pop_back();

        if (st.u != -1) {
            parent[st.v] = st.v; 
            sz[st.u] = st.sz_u;  
            sz[st.v] = st.sz_v;  
            components = st.components; 
        }
    }
};

int main() {
    setupIO();
    int n, m, k;
    if (!(cin >> n >> m >> k)) return 0; // Đọc 3 số nguyên n, m, k 

    vector<Edge> all_edges;
    vector<Edge> initial_edges(m);

    // Đọc m cạnh ban đầu của đồ thị 
    for (int i = 0; i < m; ++i) {
        int u, v;
        cin >> u >> v;
        if (u > v) swap(u, v);
        initial_edges[i] = {u, v};
        all_edges.push_back({u, v});
    }

    vector<Query> queries(k);
    vector<Edge> query_edges(k);

    // Đọc k truy vấn tiếp theo (t=1: thêm cạnh, t=2: xóa cạnh) 
    for (int i = 0; i < k; ++i) {
        int t, u, v;
        cin >> t >> u >> v;
        if (u > v) swap(u, v);
        queries[i] = {t, 0}; 
        query_edges[i] = {u, v};
        all_edges.push_back({u, v});
    }

    // Nén tọa độ cạnh
    sort(all_edges.begin(), all_edges.end());
    all_edges.erase(unique(all_edges.begin(), all_edges.end()), all_edges.end());

    int E = all_edges.size();
    vector<int> U(E), V(E);
    for (int i = 0; i < E; ++i) {
        U[i] = all_edges[i].u;
        V[i] = all_edges[i].v;
    }

    vector<bool> in_graph(E, false);

    // Đánh dấu các cạnh ban đầu đã tồn tại trong đồ thị 
    for (int i = 0; i < m; ++i) {
        int id = lower_bound(all_edges.begin(), all_edges.end(), initial_edges[i]) - all_edges.begin();
        in_graph[id] = true;
    }

    for (int i = 0; i < k; ++i) {
        int id = lower_bound(all_edges.begin(), all_edges.end(), query_edges[i]) - all_edges.begin();
        queries[i].id = id;
    }

    DSU dsu(n);

    // 1. In ra số thành phần liên thông trước khi các thao tác diễn ra 
    for (int i = 0; i < E; ++i) {
        if (in_graph[i]) dsu.unite_static(U[i], V[i]);
    }
    cout << dsu.components << " "; 

    // Bắt đầu xử lý truy vấn theo kỹ thuật Chia căn
    int B = 400; // Kích thước Block (căn bậc hai của 10^5)
    vector<bool> is_dynamic(E, false);

    for (int i = 0; i < k; i += B) {
        int L = i;
        int R = min(k - 1, i + B - 1);

        vector<int> dyn_edges;
        for (int j = L; j <= R; ++j) {
            int id = queries[j].id;
            if (!is_dynamic[id]) {
                is_dynamic[id] = true;
                dyn_edges.push_back(id);
            }
        }

        dsu.reset(n);
        // Xây dựng cây dựa trên các cạnh tĩnh
        for (int j = 0; j < E; ++j) {
            if (in_graph[j] && !is_dynamic[j]) {
                dsu.unite_static(U[j], V[j]);
            }
        }

        // Xử lý từng truy vấn trong block hiện tại
        for (int j = L; j <= R; ++j) {
            int id = queries[j].id;
            int type = queries[j].t;

            // Cập nhật trạng thái cạnh (thêm vào đồ thị nếu t=1, xóa khỏi đồ thị nếu t=2) [cite: 13]
            if (type == 1) in_graph[id] = true;  
            else if (type == 2) in_graph[id] = false;

            int unions_done = 0;
            for (int d_id : dyn_edges) {
                if (in_graph[d_id]) {
                    dsu.unite_dynamic(U[d_id], V[d_id]);
                    unions_done++;
                }
            }

            // 2. In ra số thành phần liên thông sau thao tác biến đổi 
            cout << dsu.components << (j == k - 1 ? "" : " "); 

            for (int c = 0; c < unions_done; ++c) {
                dsu.rollback();
            }
        }

        for (int id : dyn_edges) {
            is_dynamic[id] = false;
        }
    }
    cout << "\n";
    return 0;
}
2.4. Hướng tiếp cận ~O(q \log n \log q)~ – Segment Tree trên thời gian sống của cạnh

Nguồn tham khảo:

  • CP-Algorithms: Deleting from a data structure in O(T(n) log n)
  • USACO Guide: Offline Deletion

Đây là phương pháp tối ưu và phổ biến nhất cho bài toán đồ thị động với cả thêm và xóa cạnh. Ý tưởng cốt lõi là chuyển bài toán có cả thêm và xóa thành bài toán chỉ có thêm bằng cách xác định khoảng thời gian mà mỗi cạnh tồn tại, sau đó dùng một cấu trúc dữ liệu hỗ trợ thêm và quay lui (rollback) kết hợp với segment tree.

2.4.1. Xác định khoảng thời gian sống của mỗi cạnh

Giả sử ta có ~q~ thao tác (gồm ~m~ cạnh ban đầu và ~k~ truy vấn thêm/xóa). Ta đánh số các thao tác từ ~1~ đến ~q~. Với mỗi cạnh, ta xác định thời điểm nó được thêm vào (lần đầu tiên hoặc sau khi bị xóa) và thời điểm nó bị xóa (nếu có). Như vậy, mỗi cạnh sẽ tồn tại liên tục trong một hoặc nhiều đoạn thời gian. Do mỗi thao tác thêm/xóa đều tạo ra một cặp “thêm – xóa”, ta có thể biểu diễn mỗi cạnh bằng một đoạn ~[l, r)~ với ~l~ là thời điểm thêm, ~r~ là thời điểm xóa (hoặc ~q+1~ nếu không bị xóa). Nếu có nhiều lần thêm/xóa xen kẽ, mỗi lần thêm tạo một đoạn riêng.

Ví dụ:

  • Thêm cạnh (1,2) ở thời điểm 2, xóa ở thời điểm 5 → đoạn [2,5)
  • Cạnh ban đầu (3,4) không bị xóa → đoạn [1, q+1)

Việc xác định các đoạn này có thể thực hiện bằng cách duyệt qua các thao tác, dùng một map lưu thời điểm thêm gần nhất của mỗi cạnh, khi gặp thao tác xóa thì lấy thời điểm thêm tương ứng và tạo đoạn.

2.4.2. Xây dựng Segment Tree trên trục thời gian

Ta xây dựng một segment tree (cây phân đoạn) trên khoảng thời gian ~[1, q+1)~. Mỗi node của cây đại diện cho một đoạn thời gian. Với mỗi cạnh có đoạn tồn tại ~[l, r)~, ta chèn cạnh đó vào các node của segment tree sao cho đoạn ~[l, r)~ được phủ bởi các node con mà không bị dư thừa. Điều này tương tự như thao tác cập nhật đoạn trên segment tree: ta gọi hàm đệ quy, nếu đoạn của node nằm gọn trong ~[l, r)~ thì thêm cạnh vào node đó; ngược lại, tiếp tục xuống các node con. Nhờ vậy, mỗi cạnh được lưu trong ~O(\log q)~ node.

2.4.3. DSU có khả năng rollback

Vì chúng ta sẽ duyệt cây phân đoạn và thực hiện các thao tác thêm cạnh trên từng node, sau đó cần quay lui để xử lý các nhánh khác, nên DSU thông thường không đáp ứng được. Thay vào đó, ta sử dụng DSU có rollback (còn gọi là DSU với khả năng quay lui). Các đặc điểm của DSU này:

  • Không sử dụng path compression (vì nén đường làm thay đổi nhiều liên kết, khó rollback). Chỉ dùng union by size/rank.
  • Mỗi lần hợp nhất thành công, ta lưu lại các thay đổi vào một stack: ví dụ, khi gắn gốc ~a~ vào gốc ~b~, ta lưu (a, parent[a] cũ) và (b, size[b] cũ). Sau đó thực hiện gắn và cập nhật size.
  • Hàm rollback() sẽ pop hai phần tử khỏi stack và khôi phục lại trạng thái trước khi hợp nhất.
  • Ta cũng có thể lấy một snapshot (độ dài stack hiện tại) và rollback về snapshot đó.

Độ phức tạp của mỗi thao tác unite/rollback là ~O(\log n)~ (do chỉ dùng union by size và không nén).

2.4.4. Duyệt Segment Tree để trả lời truy vấn

Sau khi đã chèn tất cả các cạnh vào segment tree, ta thực hiện duyệt cây theo chiều sâu (DFS) từ node gốc, với một DSU rollback toàn cục.

void dfs(node, l, r) {
    snapshot = dsu.snapshot();
    for (cạnh trong node) {
        dsu.unite(u, v);               // thêm các cạnh của node này
    }
    if (l+1 == r) {                    // node lá, tương ứng thời điểm l
        ans[l] = dsu.components;       // ghi nhận số thành phần liên thông
    } else {
        int mid = (l+r)/2;
        dfs(node*2, l, mid);
        dfs(node*2+1, mid, r);
    }
    dsu.rollback_to(snapshot);         // quay lui để trả về trạng thái trước khi vào node
}

Giải thích:

  • Khi xuống một node, ta thực hiện tất cả các union ứng với các cạnh được lưu tại node đó.
  • Nếu node lá (ứng với một thời điểm cụ thể), lúc đó DSU đã chứa tất cả các cạnh có hiệu lực tại thời điểm đó (vì mọi cạnh có đoạn thời gian chứa thời điểm này đều đã được thêm ở một node tổ tiên nào đó).
  • Sau khi xử lý xong node con, ta rollback về trạng thái trước khi thêm các cạnh của node hiện tại, để các nhánh khác không bị ảnh hưởng.

Kết thúc quá trình, mảng ans chứa số thành phần liên thông sau mỗi thao tác (tại các thời điểm từ 1 đến q). Bài toán yêu cầu in trạng thái ban đầu (sau m cạnh ban đầu) và sau mỗi truy vấn tiếp theo, ta chỉ cần lấy các giá trị tương ứng.

2.4.5. Phân tích độ phức tạp
  • Mỗi cạnh được chèn vào ~O(\log q)~ node của segment tree.
  • Tổng số lần gọi unite trong quá trình duyệt DFS là tổng số lần cạnh xuất hiện trong các node, tức ~O((m+k) \log q)~.
  • Mỗi unite có chi phí ~O(\log n)~ do tìm gốc (không nén) và thao tác rollback cũng tốn ~O(\log n)~.
  • Do đó tổng độ phức tạp: ~O((m+k) \log q \log n)~. Với ~n, m, k \le 2\times 10^5~, thuật toán chạy rất nhanh trong thời gian cho phép.
2.4.6. Lưu ý khi cài đặt
  • Không dùng path compression: Vì nén đường sẽ thay đổi nhiều liên kết, không thể rollback hiệu quả. Chỉ dùng union by size để đảm bảo độ phức tạp ~O(\log n)~ cho find.
  • Xử lý cạnh vô hướng: Cần chuẩn hóa thứ tự hai đỉnh (ví dụ u < v) để việc lưu trong map được nhất quán.
  • Chú ý chỉ số: Nên dùng đoạn [1, q+1) để dễ quản lý, trong đó q+1 là thời điểm “sau thao tác cuối”.
  • Stack rollback: Cần đảm bảo mỗi unite thành công lưu đúng hai thay đổi, và khi rollback phải lấy đúng hai phần tử. Có thể dùng cấu trúc struct chứa loại thay đổi (parent hay size) hoặc lưu chung trong một mảng cặp.
  • Không gian bộ nhớ: Segment tree cần lưu danh sách cạnh tại mỗi node. Số lượng cạnh được lưu là ~O((m+k) \log q)~, với ~q \le 2\times 10^5~ thì bộ nhớ khoảng vài chục MB, hoàn toàn chấp nhận được.
2.4.7. Code tham khảo
#include <bits/stdc++.h>
using namespace std;

// Cấu trúc DSU hỗ trợ rollback (không dùng path compression)
struct DSUWithRollback {
    vector<int> parent, sz;   // parent và size (union by size)
    int comps;                 // số thành phần liên thông hiện tại

    // Lưu lịch sử thay đổi để rollback
    struct Change {
        int v;        // đỉnh bị thay đổi
        int old;      // giá trị cũ (parent cũ hoặc size cũ)
        bool is_parent; // true nếu thay đổi parent, false nếu thay đổi size
    };
    stack<Change> history;

    DSUWithRollback(int n) : parent(n+1), sz(n+1, 1), comps(n) {
        for (int i = 1; i <= n; ++i) parent[i] = i;
    }

    // Tìm đại diện (không nén)
    int find(int x) {
        while (parent[x] != x) x = parent[x];
        return x;
    }

    // Hợp nhất hai đỉnh, trả về true nếu có sự thay đổi
    bool unite(int a, int b) {
        a = find(a); b = find(b);
        if (a == b) return false;
        // Đảm bảo a có size nhỏ hơn để gắn a vào b
        if (sz[a] > sz[b]) swap(a, b);
        // Lưu thay đổi (push 2 change)
        history.push({a, parent[a], true});   // parent của a
        history.push({b, sz[b], false});      // size của b
        parent[a] = b;
        sz[b] += sz[a];
        --comps;
        return true;
    }

    // Rollback một lần hợp nhất (pop 2 change và tăng comps)
    void rollback() {
        Change sz_ch = history.top(); history.pop(); // size change
        Change p_ch  = history.top(); history.pop(); // parent change
        parent[p_ch.v] = p_ch.old;
        sz[sz_ch.v] = sz_ch.old;
        ++comps;
    }

    // Lấy trạng thái hiện tại (số lượng change đã lưu)
    int snapshot() const {
        return history.size();
    }

    // Rollback về trạng thái snapshot
    void rollback_to(int snap) {
        while ((int)history.size() > snap) rollback();
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m, k;
    cin >> n >> m >> k;
    int Q = m + k;                     // tổng số thao tác (1-indexed)

    // Đọc tất cả thao tác: (type, u, v)
    vector<tuple<int,int,int>> ops(Q+1);
    for (int i = 1; i <= m; ++i) {
        int u, v; cin >> u >> v;
        ops[i] = {1, u, v};
    }
    for (int i = 1; i <= k; ++i) {
        int t, u, v; cin >> t >> u >> v;
        ops[m+i] = {t, u, v};
    }

    // Xác định khoảng thời gian sống của mỗi cạnh (u,v)
    map<pair<int,int>, int> last_add; // lần thêm gần nhất
    vector<tuple<int,int,int,int>> edges; // u, v, l, r  (tồn tại trong [l, r))
    for (int i = 1; i <= Q; ++i) {
        auto [type, u, v] = ops[i];
        if (u > v) swap(u, v); // chuẩn hóa cạnh vô hướng
        if (type == 1) {       // thêm cạnh
            last_add[{u, v}] = i;
        } else {               // xóa cạnh
            auto it = last_add.find({u, v});
            if (it != last_add.end()) {
                int l = it->second;
                int r = i;
                edges.emplace_back(u, v, l, r);
                last_add.erase(it);
            }
        }
    }
    // Các cạnh còn lại không bị xóa, tồn tại đến hết
    for (auto &[uv, l] : last_add) {
        auto [u, v] = uv;
        edges.emplace_back(u, v, l, Q+1);
    }

    // Xây dựng segment tree trên các thời điểm [1, Q+1)
    int seg_sz = Q + 2;                     // đủ để bao [1, Q+1)
    vector<vector<pair<int,int>>> seg(4 * seg_sz); // mỗi node lưu danh sách cạnh (u,v)

    // Thêm cạnh vào segment tree
    function<void(int,int,int,int,int,int,int)> add_edge = [&](int node, int l, int r, int ql, int qr, int u, int v) {
        if (ql <= l && r <= qr) {
            seg[node].emplace_back(u, v);
            return;
        }
        int mid = (l + r) / 2;
        if (ql < mid) add_edge(node*2, l, mid, ql, qr, u, v);
        if (qr > mid) add_edge(node*2+1, mid, r, ql, qr, u, v);
    };
    for (auto &[u, v, l, r] : edges) {
        if (l < r) {
            add_edge(1, 1, seg_sz, l, r, u, v);
        }
    }

    DSUWithRollback dsu(n);
    vector<int> ans(Q+2, 0); // ans[i] = số thành phần liên thông sau thao tác i

    // Duyệt segment tree theo DFS
    function<void(int,int,int)> dfs = [&](int node, int l, int r) {
        int snap = dsu.snapshot();
        // Thêm tất cả cạnh tại node này
        for (auto &[u, v] : seg[node]) {
            dsu.unite(u, v);
        }
        if (r - l == 1) {       // node lá → ứng với thời điểm l
            ans[l] = dsu.comps;
        } else {
            int mid = (l + r) / 2;
            dfs(node*2, l, mid);
            dfs(node*2+1, mid, r);
        }
        dsu.rollback_to(snap); // khôi phục trạng thái trước khi vào node
    };
    dfs(1, 1, seg_sz);

    // In kết quả: sau m thao tác đầu và sau mỗi truy vấn
    cout << ans[m];
    for (int i = 1; i <= k; ++i) {
        cout << ' ' << ans[m + i];
    }
    cout << '\n';

    return 0;
}
Sunlight
o29, Tháng 3, 2025, 6:39 0

3

TỔNG KẾT SQRT #04: HÀNH TRÌNH CẢM XÚC!

Peace đã đăng vào 3, Tháng 3, 2025, 15:30

Chào buổi tối, các coder tài năng của CLB SQRT!

vậy là contest SQRT Contest #04 đã chính thức khép lại với những khoảnh khắc thi đấu đầy kịch tính và các thành tích đáng tự hào của cộng đồng lập trình viên trẻ! Hãy cùng nhìn lại những điểm nhất nổi bật của cuộc thi lần này nhé:

📊 Những con số biết nói

Cuộc thi lần này tiếp tục khẳng định sức hút của mình với sự tham gia đông đảo từ cộng đồng:

  • ~169~ thí sinh đã sẵn sàng bước vào cuộc chiến, mang đến một bầu không khí cạnh tranh sôi nổi.
  • ~5~ tiếng làm bài, ~1448~ submissions được gửi lên hệ thống, trong đó có ~145~ bài accepted - một minh chứng cho sự kiên trì và tài năng của các bạn trong suốt 300 phút đầy thử thách.
  • Với độ khó theo chuẩn Div. 1 + Div. 2 và bài toán được sắp xếp ngẫu nhiên, cuộc thi không chỉ đòi hỏi kĩ năng mà còn là sự linh hoạt về chiến lược.
  • Hệ thống chấm điểm IOI đã làm tròn vai trò của mình, ang đến sự công bằng và minh bạch cho từng dòng code mà các bạn viết nên.

Những con số này không chỉ là thống kê mà còn là câu chuyện về nỗ lực và đam mê của từng thí sinh.


💰 Giải thưởng đáng mơ ước!

Như thường lệ, CLB SQRT tiếp tục trao tặng 20 giải thưởng hấp dẫn dành cho những thí sinh xuất sắc nhất (với điều kiện có ít nhất ~5~ bài tập có điểm lớn hơn ~0~). Đây là những món quà nhỏ nhưng chứa đựng sự trân trọng lớn lao của chúng tớ:

Số lượng Giải thưởng Tiền thưởng (VNĐ) Giảm giá khi mua áo SQRT (VNĐ)
~01~ Vô địch ~80.000~ ~120.000~
~02~ Nhất ~60.000~ ~100.000~
~02~ Nhì ~50.000~ ~70.000~
~05~ Ba ~30.000~ ~50.000~
~05~ Tư ~20.000~ ~20.000~
~05~ Khuyến khích ~10.000~ ~10.000~

Những phần thưởng này không chỉ là vật chất mà còn là động lực để các bạn tiếp tục chinh phúc những thử thách mới phía trước!


📈 Cơ hội tỏa sáng trên SQRTOJ!

Ngoài giải thưởng, các thí sinh xuất sắc còn có cơ hội tăng rating trên hệ thống SQRTOJ. Đây là bước đệm để các bạn tiến gần hơn đến những mục tiêu lớn, như đổi màu handle – giấc mơ của mọi coder! Hãy tận dụng cơ hội này để khẳng định tên tuổi của mình nhé!


lời cảm ơn...

Chúng tôi xin gửi lời cảm ơn chân thành đến tất cả các bạn đã tham gia SQRT Contest #04. Dù bạn đứng ở vị trí nào trên bảng xếp hạng, mỗi dòng code, mỗi lần submit đều là minh chứng cho sự nỗ lực và đam mê của bạn. Chính các bạn đã làm nên một cuộc thi đầy ý nghĩa và đáng nhớ!

Chúc các bạn luôn giữ được ngọn lửa đam mê, tiếp tục học hỏi và bứt phá trong những sân chơi tiếp theo. CLB SQRT sẽ luôn đồng hành cùng các bạn trên chặng đường chinh phục lập trình!


Hẹn gặp lại ở SQRT contest tiếp theo nhé 🌟

👇 Tham gia cộng đồng chúng tớ tại:

SQRTOJ: SQRT Online Judge

SQRT Online Judge: SQRTOJ

SQRTOJ Community: Discord

SQRTOJ Fanpage: Facebook

SQRTOJ Group: Facebook

Peace
o3, Tháng 3, 2025, 15:30 0
  • «
  • 1
  • 2
  • »

Các kỳ thi đang diễn ra

hpprevoi27
Kết thúc trong 223 ngày 06:21:37.

Các kỳ thi sắp tới

[Chi đoàn 11 Tin, THPT Chuyên Lê Quý Đôn Quảng Trị] - Thi thử TS10 2026, lần thứ tư
Bắt đầu trong 6 ngày 14:21:37.
SQRT Cup 2026 - Vòng loại
Bắt đầu trong 34 ngày 16:21:37.

Top thành viên

# Tên truy cập
1
aaaaaa
2
imlaggs
3
MuahahaKiana
4
dungngocnhuthe
5
phucdiz
Tổ chức Xem đầy đủ >>>

Top đóng góp

# Tên truy cập Đóng góp
1
Peace
23
2
huyhau6a2
20
3
Sunlight
12
4
NhatNgu
5
5
clue_
4
Xem đầy đủ >>>

Dòng bình luận

  • Sunlight → Olympic 30 tháng 4 2026 - Khối 10 [Test tự sinh]
  • HGBCpp_ → Olympic 30 tháng 4 2026 - Khối 10 [Test tự sinh]
  • HGBCpp_ → Olympic 30 tháng 4 2026 - Khối 10 [Test tự sinh]
  • Mitpro → a quang dit bu programming league 2026
  • bobo → a quang dit bu programming league 2026
  • -testify- → a quang dit bu programming league 2026
  • yoshi_33550336 → SQRT OI 2024 - Ngày 1 - Câu 3 - Cánh cửa kho báu
  • yoshi_33550336 → a quang dit bu programming league 2026
  • -testify- → a quang dit bu programming league 2026
  • MuahahaKiana → a quang dit bu programming league 2026
RSS / Atom

Bài mới

  • Trò chơi đoán số
  • Olympic 30 tháng 4 năm 2026 - Khối 11 - Bài 1
  • Olympic 30 tháng 4 năm 2026 - Khối 11 - Bài 2
  • Olympic 30 tháng 4 năm 2026 - Khối 11 - Bài 3
  • Olympic 30 tháng 4 năm 2026 - Khối 11 - Bài 3 (Rework, Output Only version)
  • Olympic 30 tháng 4 năm 2026 - Khối 10 - Bài 1
  • Olympic 30 tháng 4 năm 2026 - Khối 10 - Bài 2
RSS / Atom

dựa trên nền tảng DMOJ và VNOI | theo dõi SQRTOJ trên Facebook