Техніка двох вказівників
Техніка двох вказівників — це стратегія, яка часто використовується в комп'ютерних науках і програмуванні для вирішення задач, пов'язаних з масивами або послідовностями. Вона полягає в тому, що використовуються два вказівники, які проходять масив або послідовність з різних позицій, часто рухаючись в протилежних напрямках або з різними швидкостями. Ця техніка особливо корисна для вирішення задач, пов'язаних з пошуком, оптимізацією або маніпуляцією масивами ефективно.
Ось детальний опис того, як працює техніка двох вказівників:
Ініціалізація: Спочатку ви встановлюєте два вказівники, зазвичай на різних позиціях в масиві або послідовності.
Рух: Ви ітеративно рухаєте вказівники на основі певних умов, поки вони не зустрінуться або не досягнуть певної умови. Рух вказівників можна контролювати на основі вимог задачі. Наприклад, один вказівник може рухатися швидше, ніж інший, або вони можуть рухатися в протилежні напрямки.
Перевірка умов: На кожному кроці ітерації ви перевіряєте певні умови на основі вимог задачі. Ці умови часто визначають, чи потрібно рухати вказівники, оновлювати деякі змінні або виконувати інші операції.
Завершення: Процес триває, поки один або обидва вказівники не досягнуть кінця масиву або послідовності, або поки не буде виконана якась інша умова завершення.
Техніка двох вказівників особливо корисна в задачах, що стосуються пошуку пар або підмасивів, які відповідають певним критеріям, знаходження оптимальних рішень або маніпуляції послідовностями ефективно без використання вкладених циклів. Вона часто забезпечує більш ефективне рішення порівняно з методами брутального перебору, особливо для задач з лінійними або логарифмічними вимогами до часу складності.
Зберігайте числа в лінійному масиві. Потім виведіть їх у зворотному порядку.
Розглянемо розв'язання для реверсії масиву з використанням техніки двох вказівників.
Встановіть дві змінні та (назвемо їх вказівниками):
на початку масиву ;
в кінці масиву ;
Далі розпочніть цикл while, в якому:
значення та обмінюються;
потім інкрементується на , а декрементується на ;
Ми продовжуємо цикл, поки вказівники та не зустрінуться.
Зверніть увагу, що значення та можуть бути обміняні за допомогою додаткової змінної , виконавши три операції:
Оголосіть масив.
#define MAX 110 int m[MAX];
Прочитайте вхідні дані.
scanf("%d",&n); for(i = 1; i <= n; i++) scanf("%d",&m[i]);
Реверсуйте масив за допомогою техніки двох вказівників.
i = 1; j = n; while(i < j) { temp = m[i]; m[i] = m[j]; m[j] = temp; i++; j--; }
Виведіть числа.
for(i = 1; i <= n; i++) printf("%d ",m[i]); printf("\n");
Хусейн та Ярослав грають у карткову гру. На столі викладено карт, кожна з яких має написане різне число. Гравці грають по черзі. Хусейн розпочинає гру. На кожному ходу гравець може взяти або найліву, або найправу карту. Гравець завжди вибирає карту з найбільшим числом. Гра закінчується, коли на столі не залишиться карт. Знайдіть суму чисел на картах, зібраних Хусейном та Ярославом наприкінці гри.
Вхідні дані. Перший рядок містить число карт на столі. Другий рядок містить позитивних цілих чисел, кожне з яких вказує число на карті. Всі числа не перевищують .
Вихідні дані. Виведіть суму чисел на картах, зібраних Хусейном та Ярославом наприкінці гри.
7 4 7 5 1 12 8 2
18 21
Нехай масив містить числа, написані на картах. Ініціалізуємо вказівники на початку масиву і в кінці масиву.
На кожному ході гравець бере карту з максимальною вартістю , після чого карта видаляється зі столу (виконуйте операцію , якщо вибрано карту , і , якщо вибрано карту ). Для кожного гравця ми відстежуємо суму обраних карт у двох змінних. Процес триває, поки всі карти не будуть видалені зі столу, тобто поки вказівники та не зустрінуться.
Приклад
Гра проходитиме наступним чином.
Оголосіть масиви. Суму чисел на картах, зібраних Хусейном та Ярославом, буде збережено в та , відповідно.
int m[10001], res[2];
Прочитайте вхідні дані.
scanf("%d", &n); for (i = 0; i < n; i++) scanf("%d", &m[i]);
Встановіть вказівники та .
i = 0; j = n - 1;
Гравці грають по черзі, здійснюючи в цілому ходів. Хусейн грає на парних , а Ярослав грає на непарних . Таким чином, сума Хусейна накопичуватиметься в , а сума Ярослава в .
for (k = 0; k < n; k++) {
На кожному ході гравець вибирає карту з максимальною вартістю .
if (m[i] > m[j]) res[k % 2] += m[i++]; else res[k % 2] += m[j--]; }
Виведіть відповідь.
printf("%d %d\n", res[0], res[1]);
Хусейн виклав карт в ряд з числами . Потім він зібрав їх і розмістив в іншому порядку: . Це послідовність, яку він дав Ярославу. Ваше завдання — допомогти Ярославу відновити початкову послідовність Хусейна.
Наприклад, якщо Ярослав отримав послідовність , тоді він повинен повернути послідовність Хусейну.
Вхідні дані. Перший рядок містить одне ціле число , що представляє кількість карт. Другий рядок містить позитивних цілих чисел, написаних на картах. Всі числа не перевищують .
Вихідні дані. Виведіть початкову послідовність Хусейна.
6 2 4 9 4 7 6
2 6 4 7 9 4
7 5 7 34 1 89 4 2
5 2 7 4 34 89 1
Нехай масив містить числа, написані на картах. Ініціалізуйте два вказівники: на початку масиву та в його кінці.
Щоб відновити початкову послідовність, по черзі беріть карти зліва і справа, поки не будуть оброблені всі елементи. З кожним вибором відповідний вказівник змінює свою позицію: рухається вправо, — вліво.
Приклад
Розглянемо кілька кроків алгоритму, використовуючи перший тестовий випадок як приклад.
Оголосіть масив.
int a[100001];
Прочитайте вхідні дані.
scanf("%d", &n); for (i = 0; i < n; i++) scanf("%d", &a[i]);
Встановіть вказівники та .
i = 0; j = n - 1;
Поки виконується нерівність , по черзі виводьте та , одночасно рухаючи вказівники.
while (i <= j) { printf("%d ", a[i++]); if (i > j) break; printf("%d ", a[j--]); }
Хусейн має рядок, що складається з символів та . Щоб розважити себе, він вигадував таку гру. Хусейн може виконати одну з двох операцій зі рядком:
додати на лівий кінець, а на правий кінець;
додати на правий кінець, а на лівий кінець;
Наприклад, з рядка Хусейн може отримати або .
Вам дано рядок, отриманий після всіх операцій Хусейна (можливо, він не виконував жодної операції). Визначте найменшу можливу довжину, яку рядок міг спочатку мати.
Вхідні дані. Один рядок довжиною не більше , що складається лише з символів та .
Вихідні дані. Виведіть найменшу можливу довжину рядка, який Хусейн міг спочатку мати.
01010010
8
1001110
1
Розглянемо вхідний рядок — отриманий рядок після того, як Хусейн виконав всі операції. Ініціалізуйте два вказівники: на початку рядка та в його кінці.
Якщо , тоді символи на кінцях рядка різні: або зліва, а справа, або зліва, а справа. Це означає, що попередній рядок міг бути розширений, застосувавши одну з вказаних операцій. У цьому випадку перемістіть обидва вказівники та на одну позицію один до одного і знову перевірте, чи можна отримати підрядок через операції Хусейна.
Як тільки поточний підрядок задовольняє умові , його більше не можна отримати за допомогою вказаних операцій. Виведіть його довжину — це початковий рядок, який мав Хусейн.
Приклад
Розглянемо операції Хусейна в зворотному порядку.
Рядок спочатку був рядком Хусейна.
Прочитайте вхідний рядок і обчисліть його довжину, зберігши її в змінній .
cin >> s; res = s.size();
Ініціалізуйте два вказівники: на початку та в кінці рядка.
i = 0; j = s.size() - 1;
Якщо , тоді символи на кінцях рядка різні. Хусейн міг отримати підрядок з підрядка .
while ((i < j) && (s[i] != s[j])) {
Перемістіть обидва вказівники та на одну позицію один до одного та зменшіть поточний розмір підрядка .
i++; j--; res -= 2; }
Виведіть відповідь.
cout << res << endl;
Дано масив , відсортований у зростаючому порядку та що містить цілих чисел. Визначте, чи існує пара чисел , де , така що їхня сума дорівнює .
Вхідні дані. Перший рядок містить два цілі числа та . Другий рядок містить невід'ємних цілих чисел, кожне з яких не більше .
Вихідні дані. Виведіть "YES", якщо така пара елементів існує, і "NO" в іншому випадку.
10 13 1 3 5 6 8 10 11 11 11 16
YES
8 61 5 5 8 12 16 21 44 50
NO
Нехай буде вхідним масивом чисел. Ініціалізуйте вказівники на початку масиву та в кінці масиву. Масив вже відсортовано відповідно до умов задачі.
Поки вказівники та не зустрінуться, виконуйте такі дії:
Якщо , тоді бажана пара елементів знайдена. Виведіть її та завершіть програму.
Якщо , перемістіть вказівник на одну позицію вправо. У цьому випадку сума зросте;
Якщо , перемістіть вказівник на одну позицію вліво. У цьому випадку зменшиться;
Приклад
Розглянемо перший тест. Ініціалізуйте вказівники. Моделюйте роботу алгоритму. Значення , ми шукаємо два числа, сума яких дорівнює .
Прочитайте вхідні дані.
scanf("%d %d", &n, &x); v.resize(n); for (i = 0; i < n; i++) scanf("%d", &v[i]);
Ініціалізуйте вказівники.
int i = 0, j = v.size() - 1;
Перемістіть вказник вперед і вказник назад.
while (i < j) {
Якщо , тоді бажана пара елементів знайдена. Виведіть її та завершіть програму.
if (v[i] + v[j] == x) { printf("YES\n"); return 0; }
Якщо , тоді перемістіть вказник на одну позицію вправо;
Якщо , тоді перемістіть вказник на одну позицію вліво;
if (v[i] + v[j] < x) i++; else j--; }
Якщо бажана пара не знайдена, виведіть "NO".
printf("NO\n");
Дано масив цілих чисел та ціле число . Знайдіть трійку чисел у масиві, чия сума дорівнює . Всі індекси повинні бути різними.
Вхідні дані. Перший рядок містить розмір масиву та значення . Другий рядок містить цілих чисел, кожне з яких не перевищує за абсолютним значенням.
Вихідні дані. Якщо потрібна трійка існує, виведіть її в будь-якому порядку. Якщо існує кілька трійок, виведіть будь-яку з них. Якщо бажана трійка не існує, виведіть .
8 19 20 3 5 1 15 7 17 12
1 3 15
Нехай буде вхідним масивом. Переберіть усі можливі індекси . Потім для кожного значення знайдіть пару індексів таку, що і . Це можна досягти у відсортованому масиві, використовуючи техніку двох вказівників за лінійний час.
Прочитайте вхідні дані.
cin >> n >> x; v.resize(n); for (i = 0; i < n; i++) cin >> v[i];
Відсортуйте вхідний масив.
sort(v.begin(), v.end());
Переберіть значення .
for (k = 0; k < n - 2; k++) {
Знайдіть пару індексів таку, що та . Ініціалізуйте індекси та .
i = k + 1; j = n - 1; s = x - v[k];
Використовуйте техніку двох вказівників для знаходження бажаної пари .
while (i < j) { if (v[i] + v[j] == s) {
Якщо пара знайдена, виведіть бажану трійку чисел .
printf("%d %d %d\n", v[k], v[i], v[j]); return 0; }
Якщо , тоді перемістіть вказник на одну позицію вправо;
Якщо , тоді перемістіть вказник на одну позицію вліво;
if (v[i] + v[j] < s) i++; else j--; } }
Якщо трійка чисел не знайдена, виведіть .
cout << -1 << endl;
Дано відсортований масив з цілих чисел. Для кожного індексу знайдіть кількість елементів у масиві, які лежать між та включно.
Вхідні дані. Перший рядок містить розмір масиву . Другий рядок містить цілих чисел, які перебувають у відсортованому порядку.
Вихідні дані. Виведіть цілих чисел. Для кожного індексу масиву виведіть кількість елементів, які лежать між та включно.
10 1 2 3 4 5 6 7 8 9 10
2 3 4 5 6 5 4 3 2 1
Інтервал буде називатися добрим, якщо числа в ньому лежать в межах включно (означає, що ).
Ми реалізуємо зсувне вікно, використовуючи два вказівники та та підтримуватимемо його так, щоб поточне інтервал був добрим, поки інтервал є поганим.
Наприклад, для наступної послідовності:
інтервали , і є добрими.
інтервали є поганими.
Якщо , розширте поточний інтервал до . В іншому випадку, скоротіть його до . Перед тим, як перемістити вказник , виведіть кількість елементів, які лежать між та включно. Вона дорівнює .
Складність алгоритму лінійна, тобто .
Приклад
Розглянемо переміщення вказівників для даного прикладу.
Для заданої умови , отже, ми переміщаємо вказівник вперед.
Тепер . Ми повинні перемістити вказник вперед. Проте, перш ніж перемістити його, виведіть кількість елементів, які лежать між та включно. Вона дорівнює .
Перемістіть вперед, поки .
Тепер . Виведіть відповідь для (вона дорівнює ) та збільшіть на .
Прочитайте вхідні дані.
scanf("%d", &n); for (i = 0; i < n; i++) scanf("%d", &a[i]);
Ініціалізуйте вказники .
i = j = 0;
Переміщуйте вказники вперед, поки для кожного значення не буде знайдено відповідь.
while (i < n) // [i..j] {
Якщо (і не перевищив межі масиву, тобто ), розширте поточний інтервал, збільшивши вказник .
if ((j < n) && (a[j] <= 2 * a[i])) j++; else {
Якщо , виведіть відповідь для індексу і збільшіть на одиницю.
printf("%d ", j - i); i++; } }
Вдоль прекрасного Адріатичного узбережжя знаходяться готелів. Кожен готель має свою вартість у євро. Петро виграв євро в лотереї. Тепер він хоче купити послідовність сусідніх готелів один за одним так, щоб сума витрат на ці сусідні готелі була якомога більшою, але не перевищувала .
Вам потрібно обчислити цю максимальну можливу загальну вартість.
Вхідні дані. Перший рядок містить два цілі числа та . Наступний рядок містить позитивних цілих чисел, які представляють витрати на готелі в порядку їх розташування уздовж узбережжя.
Вихідні дані. Виведіть бажану максимальну вартість (вона буде більшою за в усіх тестах).
5 12 2 1 3 4 5
12
Зберігайте витрати на готелі в масиві . Позначимо інтервал як добрий, якщо сума витрат у ньому не перевищує .
Реалізуємо зсувне вікно, використовуючи два індекси та , і підтримуватимемо його так, щоб поточний інтервал був добрим. Нехай буде сумою витрат у межах інтервалу . Якщо , тоді розширюємо поточний інтервал до . Інакше скорочуємо його до . Знайдіть максимум серед витрат усіх розглянутих добрих інтервалів.
Приклад
Розглянемо переміщення вказівників для даного прикладу.
Коли рухається вперед, поки сума чисел у інтервалі не перевищить . Зупиніться на інтервалі , тому що сума там дорівнює , тоді як сума в інтервалі дорівнює .
Коли , ми не можемо перемістити вперед, оскільки сума в інтервалі дорівнює .
Коли , перемістіть вперед до інтервалу .
Максимальне значення серед усіх добрих інтервалів дорівнює і досягається в інтервалі .
Прочитайте вхідні дані.
scanf("%d %lld", &n, &m);
Ініціалізуйте змінні:
в обчислюється максимальне значення серед витрат усіх оброблених добрих інтервалів;
в підтримується сума чисел у поточному інтервалі . Всі числа в поточному інтервалі зберігаються в черзі ;
s = res = 0;
Обробляйте витрати на готелі послідовно.
for (i = 0; i < n; i++) {
Прочитайте та додайте витрати поточного готелю до черги.
scanf("%d", &val); q.push(val);
Оновіть суму в межах інтервалу. Всі елементи поточного інтервалу зберігаються в черзі .
s += val;
Якщо сума чисел у поточному інтервалі перевищує , видалите елементи з початку інтервалу.
while (s > m) { s -= q.front(); q.pop(); }
Обчисліть максимальне значення серед сум елементів у добрих інтервалах (де витрати на готелі не перевищують ).
if (s > res) res = s; }
Виведіть відповідь.
printf("%lld\n", res);
Дано масив з позитивних цілих чисел, знайдіть кількість підмасивів, сума яких дорівнює .
Вхідні дані. Перший рядок містить розмір масиву та цільову суму . Наступний рядок містить цілих чисел — вміст масиву.
Вихідні дані. Виведіть необхідну кількість підмасивів.
5 7 2 4 1 2 7
3
Реалізуємо зсувне вікно, використовуючи два вказівники та . Для кожного фіксованого , розширюйте інтервал наскільки це можливо, так щоб сума його елементів не перевищувала . Іншими словами, для кожного шукайте найбільше можливе , так що сума елементів в інтервалі не перевищує , тоді як сума елементів в інтервалі перевищує .
Нехай буде сумою чисел в інтервалі . Якщо , розширте інтервал до . В іншому випадку скоротіть його до . Підрахуйте кількість інтервалів з сумою .
Приклад
Розглянемо переміщення вказників для даного прикладу.
, рухається вперед, поки сума чисел в інтервалі не перевищить . Ми зупиняємося на інтервалі , оскільки сума чисел в ньому дорівнює , тоді як в інтервалі сума чисел вже дорівнює .
, рухається вперед до . Сума чисел в інтервалі дорівнює , тоді як в інтервалі вже дорівнює .
, розглянутий інтервал — . Сума чисел у ньому дорівнює , але ми не можемо рухати індекс вперед, оскільки сума чисел в інтервалі перевищує , що є більшим, ніж .
, розглянутий інтервал — . Сума чисел у ньому дорівнює , але ми не можемо рухати індекс вперед, оскільки сума чисел в інтервалі перевищує , що є більшим, ніж .
, розглянутий інтервал — . Сума чисел у ньому дорівнює .
Кількість підмасивів з сумою дорівнює . Вони починаються на індексах та .
Оголосіть масив.
#define MAX 200001 long long a[MAX];
Прочитайте вхідні дані.
scanf("%d %lld", &n, &x); for (i = 0; i < n; i++) scanf("%lld", &a[i]);
Спочатку встановіть поточний інтервал . Сума чисел у цьому інтервалі дорівнює .
s = j = 0;
Для кожного значення послідовно обробляйте інтервали .
for (i = 0; i < n; i++) {
Для фіксованого лівого краю інтервалу шукайте найбільше , так що сума елементів у цьому інтервалі не перевищує .
while ((j < n) && (s + a[j] <= x)) s += a[j++];
Якщо сума чисел у поточному інтервалі дорівнює , збільшуйте підрахунок бажаних підмасивів на .
if (s == x) cnt++;
Перепідрахуйте суму для інтервалу .
s -= a[i]; }
Виведіть відповідь.
printf("%lld\n", cnt);
Зія візьме участь у конкурсі послідовностей завтра. Число називається вершиною деякої послідовності, якщо послідовність є підпослідовністю цієї послідовності. Сила кожної послідовності вважається її найбільшою вершиною.
Завтра всі студенти візьмуть участь у конкурсі, а переможець найсильнішої послідовності буде переможцем. Зія має послідовність . Він хоче зайняти систему оцінювання конкурсу, видаливши з неї послідовності з більшою силою, ніж у себе. Однак Зія не знає сили своєї власної послідовності, але дуже хоче виграти. Допоможіть йому обчислити силу своєї послідовності.
Вхідні дані. Перший рядок містить число чисел у послідовності Зії. Наступний рядок містить цілих чисел — елементи послідовності.
Вихідні дані. Виведіть одне число — силу даної послідовності.
2 2 10
0
3 1 2 3
1
5 1 10 2 3 1
2
Нехай буде вхідним масивом чисел. Ініціалізуйте вказівники на початку масиву та в кінці масиву. Ми будемо підраховувати силу послідовності у змінній . Спочатку встановіть .
Поки вказівники та не зустрінуться, виконуйте такі дії:
Перемістіть вказівник на одну позицію вправо, якщо він не вказує на число .
Перемістіть вказівник на одну позицію вліво, якщо він не вказує на число .
Якщо обидва вказівники вказують на число , збільшіть на одиницю та перемістіть кожен вказівник на одну позицію в відповідному напрямку.
Після завершення алгоритму відповідь буде значенням .
Приклад
Розглянемо другий тест. Ініціалізуйте вказівники. Перемістіть вказівник вправо та вказівник вліво, поки вони обидва не вкажуть на число .
Перемістіть вказник вправо та вказник вліво, поки вони обидва не вкажуть на число .
Вказівники зустрічаються, ми зупиняємо алгоритм. Відповідь буде значенням .
Прочитайте вхідні дані.
scanf("%d", &n); v.resize(n); for (i = 0; i < n; i++) scanf("%d", &v[i]);
Встановіть вказники на початок та кінець масиву .
int i = 0, j = v.size() - 1;
У змінній ми підраховуємо силу послідовності.
k = 1; while (i <= j) {
Перемістіть вказник вправо, поки він не зустріне число .
if (v[i] != k) i++;
Перемістіть вказник вліво, поки він не зустріне число .
if (v[j] != k) j--;
Якщо обидва вказники вказують на число , тоді збільшуйте на одиницю.
if (v[i] == k && v[j] == k) k++; }
Виведіть відповідь.
printf("%d\n", k - 1);