# Giao lưu THT bảng A & B # Lần 2: 26/3 ## Bảng A --- ## Vẽ hình Đặt tâm tại $(0,0)$. Bán kính lấy $r = 1$, khoảng cách giữa tâm 2 hình tròn kề nhau là $2r$ Vẽ từng lớp Giả sử ô đầu tiên tại lớp thứ $i$ là ô ở trái cùng . ## Xóa xâu Xâu chứa khác $A,B,C \Rightarrow$ `No` Điều kiện: `cnt_A + cnt_C = cnt_B` ## Kho báu Đếm số hình tại từng lớp: - Trong cùng: $1$ - Lớp $n = 1: 4$ - Lớp $n = 2: 4 \times 2$ - ... ## Chậm mà chắc *Liên tưởng tới bài toán con ốc sên: Ban ngày trèo lên $a$ mét, ban đêm tụt xuống $b$ mét.* ### Trâu AC? :open_mouth: Nhi orz Các bạn thử suy nghĩ vì sao code này đúng? ![](https://i.imgur.com/eo7e9bL.png) #### Đoạn code trên làm gì? Tìm số $i$ nhỏ nhất mà $1+2+\dots+i\,\,\,+1 \ge n$ (để ý lúc đầu $a=1$) Nói cách khác: Tổng $1..i \ge n-1$. Đáp án là $2i+1$, tức dùng $i+1$ lần bước tới và $i$ lần bước lùi. #### Phân tích & chứng minh Ở đây chỉ xét $i\ge2$ Nhận thấy nếu tổng $1..i \ge n-1$ thì chênh lệch tổng so với $n-1$ phải $< i$, vì trước đó ta có tổng $1..i-1$ vẫn còn $< n-1$ (do $i$ là số nhỏ nhất thỏa mãn). Tức là, chênh lệch của tổng **so với $n$** phải $< i-1$. Ta viết: $-1\le$ chênh lệch $< i-1$ Sau khi thêm một lần bước tới vào "ngày" hôm sau, ta có: $i \le$ chênh lệch $< i+i$. Như vậy trong $i$ lần lùi về phải bù được lượng chênh lệch này. Nhận thấy nửa đầu ($i \le$ chênh lệch) luôn thỏa mãn vì mỗi "đêm" phải lùi về tối thiểu $1$ đơn vị. Xét nửa sau, từ điều kiện này, cần có: khoảng cách tối đa lùi được $\ge$ chênh lệch, tức $1+2+\dots+i \ge 2i-1$, hay $1+2+\dots + i-1 \ge i-1$, luôn đúng. #### Nghĩ theo kiểu dãy số & tìm quy luật Xin mời Minh 9B. ### Tìm kiếm nhị phân $x$ lần di chuyển tới, sẽ được quãng đường $1+2+...+x$ Trừ đi tổng khoảng cách lùi? từ $x-1$ (mỗi ngày lùi $1$) cho tới $1 + 2 + 3 + ... + (x-1)$ Vậy khoảng cách đạt được cuối cùng nằm trong đoạn $[x, 1+2+...+x - (x-1)] = [x, x(x-1)/2 + 1]$ TKNP với $x$ từ $1$ tới $n$ ## Bảng B --- ## Bảo vệ trái đất Max dãy ## Tổng cặp tích Đoạn code cày trâu ngây thơ nhất ```cpp= int res = 0; for (int i = 1; i <= n; i++) { for (int j = i+1; j <= n; j++) { res += a[i] * a[j]; res %= MOD; } } ``` Thử viết lại code. ```cpp= int res = 0; for (int i = 1; i <= n; i++) { int sum_i = 0; for (int j = i+1; j <= n; j++) { sum_i += a[i] * a[j]; sum_i %= MOD; } res += sum_i; } ``` Tách bạch tổng cần tính ra theo từng $i$, thấy được cần tính $a_i \cdot a_{i+1} + a_i \cdot a_{i+2} + a_i \cdot a_{i+3} + \dots + a_i \cdot a_n$. Đặt nhân tử chung, cần tính $a_i \cdot(a_{i+1} + a_{i+2} + \dots + a_n)$ Vậy với mỗi số thứ $i$, cần tính tổng tất cả $a_j$ đằng sau nó. Để tính nhanh, dùng prefix-sum, nhưng thật ra không cần thiết. Chỉ cần chạy `for` ngược về, duy trì một biến tổng. ```python= int sum_after = 0, res = 0; for (int i = n; i > 0; i--) { res += a[i] * sum_after; sum_after += a[i]; } ``` ## Tổng mũ Cần tính nhanh $a^b \mod c$ bằng chia để trị ### Ý tưởng https://viblo.asia/p/phep-nhan-an-do-thuat-toan-binh-phuong-va-nhan-gDVK2dmrlLj Để tính $a^b$, ta tính từ $a^{b/2}$. Sau $O(1)$ phép tính, số mũ giảm đi một nửa. Do đó ĐPT là $O(\log)$ ```python M = 10**9 def luythua(a,b): if b == 0: return 1 if b % 2 != 0: return a * luythua(a,b-1) % M T = luythua(a, b//2) return T*T % M ``` Ý tưởng được thể hiện trong đoạn code trên: - $a^0 = 1$ - Nếu $b$ chẵn thì chỉ cần **một** lần tính $a^{b/2}$, rồi bình phương kết quả này để có $a^b$) - Nếu $b$ lẻ thì đưa về trường hợp $b$ chẵn - Lưu ý: Nếu không đặt biến `tạm = lũy_thừa(a, b/2)` mà code thẳng luôn `return luythua(a,b/2) * luythua(a,b/2)` sẽ bị TLE (ĐPT $O(b)$ thay vì $O(\log)$). ### Đặc quyền Python user Chỉ cần gọi `pow(a,b,c)` thì sẽ tự động có được thuật toán xịn (chia để trị) với ĐPT $O(\log c)$ ## Leo thang Bài toán: Tìm vị trí đầu tiên trong $a$ mà $>t$. Đặt $M_i$ là $\max$ của $i$ phần tử đầu tiên. Cần TKNP trên mảng $M$ ra vị trí $j$ lớn nhất mà $M_j \le t$. Thấy rằng $M$ tăng dần và $j+1$ là vị trí đầu tiên $>t$, tức $M_{j+1}$ (hay $A_{j+1}$) đều $>t$. ### Giải thích kỹ hơn Thử "chày cối" TKNP giống subtask 2. Điều kiện cần đặt ra bây giờ không phải là `if a[mid] < t` nữa, mà phải là `mọi số từ 1 tới mid có < t hay không?`. Để tính nhanh được $\max$ của các số từ đầu tới một vị trí $i$ nào đó, ta áp dụng ý tưởng của prefix-sum: $M_i = \max(M_{i-1}, a_i)$ ## Chia kẹo ### Sub 1: Mỗi gói kẹo có hai lựa chọn: cho shiba hoặc minhduc. Coi như xâu nhị phân 0/1. Đệ quy quay lui $O(2^n)$ #### Cụ thể `void backtrack(int i, long long shiba)` Đã xét được $i$ gói kẹo đầu tiên, tổng mà Shiba nhận được là `shiba`. Tới cuối cùng, ta lấy tổng $n$ số trừ cho `shiba` ### Sub 2: Đặt $\texttt{dp[i][j] = true/false}$ là xem thử trong $i$ gói kẹo đầu tiên ta có lấy ra được một vài gói có tổng $j$ hay không? #### Chuyển trạng thái: Đang ở trạng thái `dp[i][j]`, mà `dp[i][j] = true`. Xét hai trường hợp: - TH 1: Thêm gói kẹo thứ $i$ cho người hiện tại. Gán `dp[i+1][j + a[i]] = true` - TH 2: Gán `dp[i+1][j] = true` #### Lấy ra đáp án Nếu `dp[n+1][j] = true` thì .... ### Sub 3: Duyệt phân tập (meet in the middle). ![](https://i.imgur.com/263qunt.png) ### Sub 4: Tối ưu sub2 bằng bitset. Vì thông tin ta lưu là biến boolean true/false nên thay vào đó có thể dùng số nguyên để biểu diễn. Một số int có 32 bit tương ứng với 32 biến boolean, 32 vị trí trong mảng $dp[i]$. ![](https://i.imgur.com/VKyfoBX.png)