Автор задачі: | Андрій Холод (Connect) |
Підготував: | Андрій Холод (Connect) |
Розбір: | Андрій Холод (Connect) |
Ідея
Кожна спіраль задається лише початковою точкою (x_i,y_i). Усе інше в неї однакове: ті самі напрямки, ті самі довжини відрізків, ті самі кути.
Тобто будь-яка спіраль — це просто одна й та сама канонічна спіраль, зсунута на вектор (x_i,y_i).
Тому замість того, щоб працювати з різними спіралями окремо, достатньо:
побудувати одну канонічну спіраль із початком у ;
для кожного її відрізка порахувати, скільки стартових точок переводять цей відрізок у точку запиту.
1) Канонічна спіраль
Побудуємо спіраль, що починається в .
Нехай її -й відрізок має один із чотирьох напрямків: [ (1,0),\ (0,1),\ (-1,0),\ (0,-1) ] а довжина дорівнює f_i.
Код просто симулює перші відрізки цієї ламаної і зберігає:
усі горизонтальні та вертикальні відрізки;
усі точки зламу (corners).
Для кожного відрізка зберігається:
його тип: горизонтальний чи вертикальний;
фіксована координата;
межі по другій координаті.
2) Чому достатньо лише перших 55 відрізків
Це ключовий момент.
Початкові точки спіралей задовольняють: |x_i|, |y_i| \le 10^9 а точки запитів: |x|, |y| \le 10^7
Отже якщо точка лежить на спіралі з початком у (x_i,y_i), то відповідна точка на канонічній спіралі має координати (x - x_i,\ y - y_i) тобто |x - x_i| \le 1.01 \cdot 10^9, |y - y_i| \le 1.01 \cdot 10^9.
А координати канонічної спіралі ростуть приблизно як числа Фібоначчі. Уже через кілька десятків кроків вони за модулем стають значно більшими за 10^9. Тому жоден пізніший відрізок уже не може дати точку, яка після зсуву потрапить у жоден запит.
У коді взято запас із 55 відрізків, цього більш ніж достатньо.
3) Як порахувати внесок одного горизонтального відрізка
Нехай у канонічній спіралі є горизонтальний відрізок: y = c, x \in [L,R].
Після зсуву на стартову точку (s_x,s_y) він стане: y = s_y + c, x \in [s_x + L,\ s_x + R].
Точка запиту (q_x,q_y) лежить на цьому зсунутому відрізку тоді й лише тоді, коли:
q_y = s_y + c і q_x \in [s_x + L,\ s_x + R].
Перепишемо це як умови на стартову точку:
s_y = q_y - c і s_x \in [q_x - R,\ q_x - L].
Тобто для кожного горизонтального відрізка задача зводиться до такого запиту:
Порахувати, скільки стартових точок мають y = q_y - c i x \in [q_x - R,\ q_x - L].
4) Як порахувати внесок вертикального відрізка
Аналогічно, якщо канонічний відрізок вертикальний: x = c, y \in [L,R], то після зсуву на (s_x,s_y) він стане: x = s_x + c, y \in [s_y + L,\ s_y + R].
Точка (q_x,q_y) лежить на ньому тоді й лише тоді, коли: s_x = q_x - c і s_y \in [q_y - R,\ q_y - L].
Тобто треба порахувати кількість стартових точок із x = q_x - c та y \in [q_y - R,\ q_y - L].
5) Як швидко рахувати такі точки
Маємо стартові точки спіралей: (x_i,y_i).
Можна будувати два масиви:
by_X: пари (x_i,y_i), відсортовані лексикографічно;
by_Y: пари (y_i,x_i), також відсортовані.
Тоді:
для вертикального відрізка ми фіксуємо і шукаємо всі у проміжку;
для горизонтального відрізка ми фіксуємо і шукаємо всі у проміжку.
Це робиться двома бінарними пошуками:
lower_bound — початок підходящого діапазону;
upper_bound — кінець.
Кількість точок у цьому діапазоні й додається до відповіді.
6) Чому треба віднімати кути
Якщо точка запиту потрапляє рівно в точку зламу спіралі, то вона належить двом сусіднім відрізкам одночасно. Тоді ми порахуємо цю саму спіраль двічі:
один раз через попередній відрізок;
ще раз через наступний.
Тому після підрахунку всіх відрізків треба відняти кількість спіралей, для яких запит потрапив у вершину канонічної спіралі.
Це можна підтримувати окремим масивом
Якщо вершина канонічної спіралі має координати (c_x,c_y), то для запиту (q_x,q_y) шукаємо стартові точки: (s_x,s_y) = (q_x - c_x,\ q_y - c_y).
Кількість таких стартів уже була врахована двічі, тому її потрібно відняти один раз.
9) Асимптотика
Нехай K=55 — кількість згенерованих відрізків.
Попередня обробка:
сортування стартових точок — O(n \log n);
побудова канонічної спіралі — .
Один запит:
проходимо по всіх відрізках;
для кожного робимо O(\log n) через бінарні пошуки;
окремо проходимо по всіх K-1 кутах.
Отже один запит працює за: O(K \log n), а оскільки — константа, це просто O(\log n).
Сумарно: O(n \log n + q \log n).
Код рішення
#include <bits/stdc++.h>
#include <random>
// fast_pragmas
//#pragma GCC optimize("O3")
//#pragma GCC optimize("Ofast")
//#pragma GCC optimize ("unroll-loops")
//#pragma GCC target("avx,avx2,tune=native")
// fast_pragmas ended
//#pragma comment(linker, "/stack:200000000")
//#pragma GCC optimize("Ofast")
//#pragma GCC optimize("O3,unroll-loops")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4")
//#pragma GCC target("avx2,bmi,bmi2,popcnt,lzcnt")
using namespace std;
#define ull unsigned long long
#define ll long long
#define ld long double
#define speed ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define endl '\n'
#define yes cout<<"YES"<<endl
#define no cout<<"NO"<<endl
#define pb push_back
#define ln log
#define lg log10
#define log log2
#define here cout<<"HERE"<<endl
const ll clasik = 5e5 + 7;
const ll mln = 1e6 + 7;
const ll mod = 1e9 + 7;
//const ll mod = 998244353;
const ll block_size = 450;
const ll INF = 1e17;
ll df[clasik];
struct Segment {
char type;
ll fixed;
ll min_val;
ll max_val;
};
void solve() {
ll n;
cin >> n;
vector<pair<ll, ll>> by_X(n), by_Y(n);
ll min_x = (ll)2e18, max_x = -(ll)2e18;
ll min_y = (ll)2e18, max_y = -(ll)2e18;
for (int i = 0; i < n; i++) {
ll x, y;
cin >> x >> y;
by_X[i] = {x, y};
by_Y[i] = {y, x};
min_x = min(min_x, x);
max_x = max(max_x, x);
min_y = min(min_y, y);
max_y = max(max_y, y);
}
sort(by_X.begin(), by_X.end());
sort(by_Y.begin(), by_Y.end());
vector<ll> f(60);
f[1] = 1;
f[2] = 1;
for (int i = 3; i <= 55; i++) {
f[i] = f[i - 1] + f[i - 2];
}
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, 1, 0, -1};
vector<Segment> segs;
vector<pair<ll, ll>> corners;
ll cx = 0, cy = 0;
for (int i = 1; i <= 55; i++) {
ll nx = cx + f[i] * dx[(i - 1) % 4];
ll ny = cy + f[i] * dy[(i - 1) % 4];
if ((i - 1) % 2 == 0) {
segs.pb({'H', cy, min(cx, nx), max(cx, nx)});
} else {
segs.pb({'V', cx, min(cy, ny), max(cy, ny)});
}
if (i < 55) {
corners.pb({nx, ny});
}
cx = nx;
cy = ny;
}
ll q;
cin >> q;
while (q--) {
ll qx, qy;
cin >> qx >> qy;
ll ans = 0;
for (auto seg : segs) {
if (seg.type == 'H') {
ll py = qy - seg.fixed;
ll px_min = qx - seg.max_val;
ll px_max = qx - seg.min_val;
if (py >= min_y && py <= max_y && px_max >= min_x && px_min <= max_x) {
auto it_start = lower_bound(by_Y.begin(), by_Y.end(), make_pair(py, px_min));
if (it_start != by_Y.end() && it_start->first == py && it_start->second <= px_max) {
auto it_end = upper_bound(it_start, by_Y.end(), make_pair(py, px_max));
ans += distance(it_start, it_end);
}
}
} else {
ll px = qx - seg.fixed;
ll py_min = qy - seg.max_val;
ll py_max = qy - seg.min_val;
if (px >= min_x && px <= max_x && py_max >= min_y && py_min <= max_y) {
auto it_start = lower_bound(by_X.begin(), by_X.end(), make_pair(px, py_min));
if (it_start != by_X.end() && it_start->first == px && it_start->second <= py_max) {
auto it_end = upper_bound(it_start, by_X.end(), make_pair(px, py_max));
ans += distance(it_start, it_end);
}
}
}
}
for (auto c : corners) {
ll px = qx - c.first;
ll py = qy - c.second;
if (px >= min_x && px <= max_x && py >= min_y && py <= max_y) {
auto it_start = lower_bound(by_X.begin(), by_X.end(), make_pair(px, py));
if (it_start != by_X.end() && it_start->first == px && it_start->second == py) {
auto it_end = upper_bound(it_start, by_X.end(), make_pair(px, py));
ans -= distance(it_start, it_end);
}
}
}
cout << ans << endl;
}
}
signed main() {
//freopen("deciding_points_input.txt" , "r" , stdin);
//freopen("outputA.txt" , "w" , stdout);
speed;
int test;
test = 1;
//precalc();
//cin >> test;
for (int tt = 1; tt <= test; tt++) {
//cout << "Case #" << tt << ": ";
solve();
}
return 0;
}