---
title: Kỹ thuật 2 con trỏ & Bài tập vận dụng (Hướng dẫn giải bài tập)
---
Kỹ thuật 2 con trỏ & Bài tập vận dụng (Hướng dẫn giải bài tập)
===
-----
###### ✍️ Author: 2School Guideline
###### 📋 Content:
[TOC]
-----
# Bài 1: [CSES - Apartments](https://cses.fi/problemset/task/1084)
## Lời giải
- Để tối ưu được số người dân có nhà ở, chúng ta sẽ xử lí theo kiểu cho người dân với yêu cầu nhỏ thì họ sẽ nhận căn hộ nhỏ.
- Lúc này ta cần sắp xếp lại 2 mảng $a$ và $b$.
- Ta dùng 2 con trỏ $i$ và $j$ lần lượt xuất phát từ đầu 2 mảng $a$ và $b$ để xác định người dân và căn hộ thỏa mãn tương ứng.
- Nếu căn hộ thỏa mãn người dân hiện tại, cư dân đó sẽ lấy căn hộ này và số lượng cư dân có nhà ở tăng lên 1.
- Nếu căn hộ thứ $j$ có độ thẩm mỹ vượt quá mức độ chịu đựng của người dân $i$ thì ta xét đến yêu cầu của người dân $i+1$ - người dân có yêu cầu về thẩm mỹ cao hơn.
- Nếu căn hộ thứ $j$ chưa thỏa yêu cầu của cư dân $i$ thì ta xét đến căn hộ $j+1$ - căn hộ có độ thẩm mỹ cao hơn.
- Độ phức tạp: $O(n \log n + m\log m)$.
## Code mẫu
```cpp=
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, m, k, a[N], b[N];
void solve(){
sort(a, a + n); sort(b, b + m);
int i, j, ans; i = j = ans = 0;
while (i < n && j < m)
if (a[i] - k <= b[j] && b[j] <= a[i] + k){
++ans;
++i;
++j;
} else if (b[j] < a[i] - k) ++j;
else ++i;
cout << ans;
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n >> m >> k;
for (int i = 0; i < n; ++i) cin >> a[i];
for (int i = 0; i < m; ++i) cin >> b[i];
solve();
return 0;
}
```
# Bài 2: [VNOJ - VMQUABEO](https://oj.vnoi.info/problem/vmquabeo)
## Lời giải
- Chúng ta hãy cùng nhìn vào một vài yếu tố mà đề đã cho:
- Admin K chỉ chấp nhận đoạn đường từ $[t, p]$ để chạy khi $max(h[t]$ ... $h[p]) - min(h[t]$ ... $h[p]) \leq D$
$=>$ Chúng ta cần một cấu trúc nào đó có khả năng tìm ra min và max của một đoạn nhanh nhất có thể $=>$ **set**
- Đoạn đường $[t, p]$ phải có độ dài tối thiểu là $L$ $=>$ đoạn đường phải có tối thiểu $L + 1$ điểm (nói cách khác, $p - t \geq L$)
- Qua những nhận xét trên, ta có 1 cách làm như sau:
- B1: Gọi con trỏ $t$ và $p$ lần lượt là con trỏ chỉ vị trí trái và phải của đoạn đường mà admin K chọn. Ban đầu con trỏ $t$ chỉ vào vị trí 0.
- B2: Cho con trỏ $p$ tăng đều sao cho tới khi thoả điều kiện $p - t \geq L$. Trong quá trình này, ta đồng thời thêm $h[p]$ vào **set** để tính toán sau.
- B3.1: Nếu như có cặp $[t, p]$ thoả mãn, số đoạn con khác nhau kết thúc ở $p$ thoả mãn yêu cầu $max(h[t]$ ... $h[p]) - min(h[t]$ ... $h[p]) \leq D$ là $p - t - l + 1$. Ta sẽ cộng đáp án vào biến $ans$ ( Vì $p$ tăng đều và mỗi lần ta chỉ lấy các đoạn kết thúc ở $p$ nên ta có thể lấy được tất cả các trường hợp mà không bị trùng đoạn nào. )
- B3.2: Ở trường hợp còn lại khi không có cặp $[t, p]$ thoả mãn nào, chúng ta sẽ tăng con trỏ $t$ lên 1 và xoá đi $h[t]$ trong **set** ( Ta có thể biết con trỏ $t$ luôn tăng chứ không thể giảm vì nếu $h[t] ... h[p]$ đã không thỏa điều kiện thì $h[t] ... h[p+1]$ chắc chắn cũng không thỏa. )
- B4: lặp lại B2 cho tới khi $p = n$
- Đáp án đề bài là $ans$
- Độ phức tạp: $O(n \log n)$.
## Code mẫu
```cpp=
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n, l, d, h[N];
long long ans = 0;
multiset<int> s;
void solve(){
int t = 0, p = -1;
while (p - t < l) {
p++;
s.insert(h[p]);
}
while (p < n) {
int lo = *s.begin(), hi = *s.rbegin();
if (hi - lo <= d){
ans += p - t - l + 1;
p++;
s.insert(h[p]);
}
else {
s.erase(s.find(h[t]));
t++;
if (p - t < l){
p++;
s.insert(h[p]);
}
}
}
cout << ans;
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
cin >> n >> l >> d;
for (int i = 0; i < n; i++) cin >> h[i];
solve();
return 0;
}
```
# Bài 3: [VNOJ - NKSGAME](https://oj.vnoi.info/problem/nksgame)
## Lời giải
- **Nhận xét** : để trị tuyệt đối của tổng $2$ phần tử trong $2$ mảng là bé nhất tương đương với việc ta cố gắng lấy $2$ phần tử sao cho tổng của chúng $= 0$.
- Điều này đồng nghĩa với mỗi $b[i]$, mình cần tìm $c[i]$ sao cho $c[i]$ gần với $-b[i]$ nhất.
- Để làm được điều này một cách hiệu quả, mình sẽ sử dụng $2$ con trỏ ngược chiều trên $2$ mảng $b$, $c$ sau khi đã được sắp xếp tăng dần.
- Khởi tạo $1$ con trỏ $i$ chạy từ đầu đến đuôi mảng $b$, con trỏ $j$ chạy từ đuôi đến đầu mảng $c$.
- Với mỗi $b[i]$, mình sẽ dời con trỏ $j$ xuống sao cho trị tuyệt đối của tổng của chúng là bé nhất có thể. Khi trị tuyệt đối của tổng $2$ phần tử là tối ưu (không thể dời con trỏ $j$ xuống để giảm trị tuyết đối của tổng nữa), mình dừng thao tác dời $j$ xuống, đồng thời tăng $i$ để xét vị trí $b[i]$ kế tiếp.
- Độ phức tạp : $O(n \log n)$.
## Code mẫu
```cpp=
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5;
int n;
int b[N+1], c[N+1];
void read(){
cin >> n;
for(int i = 1; i <= n; ++i) cin >> b[i];
for(int i = 1; i <= n; ++i) cin >> c[i];
sort(b + 1, b + n + 1);
sort(c + 1, c + n + 1);
}
void solve(){
int i = 1, j = n;
int ans = abs(b[i] + c[j]);
while(i <= n && j >= 1){
while(j > 1 && abs(b[i] + c[j-1]) <= abs(b[i] + c[j])) --j;
ans = min(ans, abs(b[i] + c[j]));
++i;
}
cout << ans;
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
read();
solve();
return 0;
}
```
# Bài 4: [CSES - Ferris Wheel](https://cses.fi/problemset/task/1090)
## Lời giải
- Nhận thấy rằng: để tối ưu hoá, với mỗi $a[i]$, chúng ta cần tìm $a[j]$ sao cho $(a[i] + a[j])_m$$_a$$_x$ và $a[i] + a[j] \leq x$.
- Để thực hiện điều này, mình sẽ sử dụng $2$ con trỏ ngược chiều trên mảng $a[]$ đã được sắp xếp tăng dần.
- Khởi tạo con trỏ $i$ đi từ đầu đến cuối mảng, con trỏ $j$ đi từ cuối đến đầu mảng.
- Với mỗi cặp $(i,j)$, sẽ có $2$ trường hợp xảy ra:
- Trường hợp $1$ : $a[i] + a[j] ≤ x$ : khi này cặp $(i, j)$ thoả, ta gom vào $1$ nhóm, đồng thời tăng $i$ lên 1, giảm $j$ đi $1$ để xét $2$ phần tử kế tiếp.
- Trường hợp $2$ : $a[i] + a[j] > x$ : vì cặp $(i, j)$ không thoả, khi này mình cho $a[j]$ vào $1$ nhóm, đồng thời giảm $j$ đi $1$ để có $a[j]$ bé hơn.
- Độ phức tạp : $O(n \log n)$.
## Code mẫu
```cpp=
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
int n, x;
int a[N + 1];
void read(){
cin >> n >> x;
for(int i = 1; i <= n; ++i) cin >> a[i];
sort(a + 1, a + n + 1);
}
void solve(){
int ans = 0;
int i = 1, j = n;
while(i <= j){
if(a[i] + a[j] <= x) ++i;
--j;
++ans;
}
cout << ans;
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
read();
solve();
return 0;
}
```
# Bài 5: [CSES - Sum of Two Values](https://cses.fi/problemset/task/1640)
## Lời giải
- Vì đây là $1$ bài toán trong số các bài toán giới thiệu $2$ con trỏ ngược chiều, nên các bạn có thể xem lời giải ở bài viết giới thiệu $2$ con trỏ nhé.
- Độ phức tạp : $O(n \log n)$.
## Code mẫu
```cpp!
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
int n, x;
pair <int, int> a[N + 1];
///a[i].first = giá trị
///a[i].second = vị trí ban đầu
void read(){
cin >> n >> x;
for(int i = 1; i <= n; ++i){
cin >> a[i].first;
a[i].second = i;
}
sort(a + 1, a + n + 1);
}
void solve(){
int i = 1, j = n;
while(i < j){
if(a[i].first + a[j].first == x){
cout << a[i].second << " " << a[j].second;
return;
}
else if(a[i].first + a[j].first > x) --j;
else if(a[i].first + a[j].first < x) ++i;
}
cout << "IMPOSSIBLE";
}
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
read();
solve();
return 0;
}
```