---
# System prepended metadata

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)

---

---
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;
}


```