ограничение по времени на тест 2 секунды ограничен...
Tạo vào: 16 tháng 7, 2026
Tạo vào: 16 tháng 7, 2026
ограничение по времени на тест
2 секунды
ограничение по памяти на тест
256 мегабайт
Дано неориентированное дерево, состоящее из n вершин. Простой путь p длины k определяется как последовательность различных вершин p0,p1,…,pk такая, что между вершинами pi и pi+1 существует неориентированное ребро для каждого 0≤i<k. Простой путь однозначно задаётся неупорядоченной парой своих концов (p0,pk). То есть (u,v) и (v,u) представляют один и тот же простой путь.
Том и Джерри играют в игру на этом дереве. Игроки ходят по очереди, первым ходит Том. На i-м ходе (i≥1) текущий игрок выбирает простой путь (xi,yi), который удовлетворяет следующим условиям:
xi≠yi.
Путь не имеет общих рёбер ни с одним из ранее выбранных путей.
Либо xi, либо yi принадлежит пути, выбранному на (i−1)-м ходе. Заметим, что это условие применяется только при i≥2.
Игрок, который не может выбрать допустимый путь на своём ходе, проигрывает.
Предполагая, что Том и Джерри играют оптимально, ваша задача — вычислить общее количество различных простых путей, которые Том может выбрать на первом ходе так, чтобы он мог гарантировать победу.
Входные данные
Каждый тест состоит из нескольких наборов входных данных. В первой строке находится одно целое число t (1≤t≤104) — количество наборов входных данных. Далее следует описание наборов входных данных.
В первой строке каждого набора входных данных дано целое число n (2≤n≤2⋅105), обозначающее количество вершин в дереве.
В каждой из следующих n−1 строк даны два целых числа u и v (1≤u,v≤n, u≠v), обозначающие неориентированное ребро между вершинами u и v. Гарантируется, что рёбра образуют корректное дерево.
Гарантируется, что сумма значений n по всем наборам входных данных не превосходит 2⋅105.
Выходные данные
Для каждого набора входных данных выведите целое число, обозначающее общее количество различных простых путей, которые Том может выбрать на первом ходе так, чтобы он мог гарантировать победу.
#include <iostream>
#include <vector>
using namespace std;
int k = 0;
vector<int> st;
vector<vector<int>> gr;
vector<bool> used;
void dfs_nch(int v) {
used[v] = true;
k++;
textfor (auto u : gr[v]) { if (used[u] || st[u] % 2 == 0) continue; dfs_nch(u); }
}
void dfs_ch(int v) {
used[v] = true;
textfor (auto u : gr[v]) { if (st[u] % 2 == 1) { k++; continue; } else if (used[u]) continue; dfs_ch(u); }
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
textint t; cin >> t; while (t--) { int n; cin >> n; used.assign(n + 1, false); st.assign(n + 1, 0); gr.assign(n + 1, vector<int> (0, 0)); for (int i = 1; i < n; ++i) { int v, u; cin >> v >> u; gr[v].push_back(u); gr[u].push_back(v); st[v]++; st[u]++; } long long ans = 0; for (int i = 1; i <= n; ++i) { if (st[i] % 2 == 0 || used[i]) continue; dfs_nch(i); ans += k - 1; k = 0; } for (int i = 1; i <= n; ++i) { if (st[i] % 2 == 1 || used[i]) continue; dfs_ch(i); ans = k * (k - 1) / 2; k = 0; } cout << ans << '\n'; }
}
исправь мой код, МИНИМАЛЬНО его изменяя (без комментариев) и очень подробно объясняя что не так
pythonfrom functools import lru_cache from itertools import combinations def paths_tree(n, edges): adj=[[] for _ in range(n)] for i,(a,b) in enumerate(edges): adj[a].append((b,i));adj[b].append((a,i)) paths=[] for s in range(n): parent=[(-1,-1)]*n stack=[s]; parent[s]=(s,-1) for v in stack: for u,eid in adj[v]: if parent[u][0]==-1: parent[u]=(v,eid);stack.append(u) for t in range(s+1,n): verts=[] emask=0 x=t while x!=s: verts.append(x) p,eid=parent[x] emask|=1<<eid x=p verts.append(s) verts=verts[::-1] paths.append((s,t,emask,frozenset(verts))) return paths def winning_first(n,edges): ps=paths_tree(n,edges) @lru_cache(None) def win(used, prev_idx): prev=ps[prev_idx] for j,q in enumerate(ps): if used&q[2]: continue if q[0] in prev[3] or q[1] in prev[3]: if not win(used|q[2],j): return True return False wins=[] for i,p in enumerate(ps): # Jerry turn; Tom wins if state is losing for next if not win(p[2],i): wins.append((p[0],p[1])) return wins def formula(n,edges): deg=[0]*n; adj=[[] for _ in range(n)] for a,b in edges: deg[a]+=1;deg[b]+=1;adj[a].append(b);adj[b].append(a) used=[False]*n ans=0 for i in range(n): if deg[i]%2==0 or used[i]: continue k=0; st=[i];used[i]=1 while st: v=st.pop();k+=1 for u in adj[v]: if not used[u] and deg[u]%2==1: used[u]=1;st.append(u) ans+=k-1 for i in range(n): if deg[i]%2==1 or used[i]: continue k=0; st=[i];used[i]=1 while st: v=st.pop() for u in adj[v]: if deg[u]%2==1:k+=1 elif not used[u]: used[u]=1;st.append(u) ans+=k*(k-1)//2 return ans # all labeled trees via Prufer up to 6 import itertools def prufer_to_edges(seq,n): deg=[1]*n for x in seq:deg[x]+=1 edges=[] import heapq leaves=[i for i,d in enumerate(deg) if d==1];heapq.heapify(leaves) for x in seq: l=heapq.heappop(leaves);edges.append((l,x)) deg[l]-=1;deg[x]-=1 if deg[x]==1:heapq.heappush(leaves,x) a=heapq.heappop(leaves);b=heapq.heappop(leaves);edges.append((a,b)) return edges for n in range(2,8): ok=True for seq in itertools.product(range(n), repeat=max(0,n-2)): ed=prufer_to_edges(seq,n) if n>2 else [(0,1)] w=len(winning_first(n,ed)) f=formula(n,ed) if w!=f: print("mismatch",n,ed,w,f,winning_first(n,ed)) ok=False;break print(n,ok) if not ok:break
Ваша основная идея правильная. Вы считаете пути, у которых:
Ниже исправленная версия с минимальным сохранением вашей структуры. Рекурсивные DFS заменены на итеративные, потому что при n = 2⋅10^5 рекурсия может привести к переполнению стека.
cpp#include <iostream> #include <vector> using namespace std; int k = 0; vector<int> st; vector<vector<int>> gr; vector<bool> used; void dfs_nch(int v) { vector<int> q = {v}; used[v] = true; while (!q.empty()) { v = q.back(); q.pop_back(); k++; for (auto u : gr[v]) { if (used[u] || st[u] % 2 == 0) continue; used[u] = true; q.push_back(u); } } } void dfs_ch(int v) { vector<int> q = {v}; used[v] = true; while (!q.empty()) { v = q.back(); q.pop_back(); for (auto u : gr[v]) { if (st[u] % 2 == 1) { k++; continue; } if (used[u]) continue; used[u] = true; q.push_back(u); } } } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n; cin >> n; used.assign(n + 1, false); st.assign(n + 1, 0); gr.assign(n + 1, {}); for (int i = 1; i < n; ++i) { int v, u; cin >> v >> u; gr[v].push_back(u); gr[u].push_back(v); st[v]++; st[u]++; } long long ans = 0; for (int i = 1; i <= n; ++i) { if (st[i] % 2 == 0 || used[i]) continue; dfs_nch(i); ans += k - 1; k = 0; } for (int i = 1; i <= n; ++i) { if (st[i] % 2 == 1 || used[i]) continue; dfs_ch(i); ans += 1LL * k * (k - 1) / 2; k = 0; } cout << ans << '\n'; } }
Ключевой игровой факт:
Первый путь является выигрышным для Тома тогда и только тогда, когда его концы имеют нечётные степени, а все внутренние вершины имеют чётные степени.
Для конечной вершины пути используется одно инцидентное ребро. Поэтому после первого хода у неё остаётся:
свободных рёбер. Это число должно быть чётным, следовательно, исходная степень конца должна быть нечётной.
Для внутренней вершины пути используются два ребра. Остаётся:
свободных рёбер. Это число должно быть чётным, следовательно, степень внутренней вершины должна быть чётной.
Эта чётность позволяет Тому после первого хода применять ответную стратегию: свободные продолжения можно разбивать на пары. На каждый ход Джерри Том выбирает соответствующее парное продолжение. В итоге без хода остаётся Джерри.
Если хотя бы один конец имеет чётную степень либо внутренняя вершина имеет нечётную степень, такую парную стратегию получает уже Джерри.
Таким образом, нужно посчитать пути следующего вида:
Количество внутренних чётных вершин может быть нулевым.
cppdfs_nch(i); ans += k - 1;
Здесь рассматриваются компоненты, состоящие только из вершин нечётной степени.
Поскольку исходный граф — дерево, каждая такая компонента также является деревом.
Если в компоненте k вершин, внутри неё имеется ровно:
рёбер.
Каждое такое ребро само является допустимым путём длины 1:
textнечётная — нечётная
У него нет внутренних вершин, поэтому условие на внутренние чётные вершины автоматически выполняется.
Именно поэтому добавляется:
cppans += k - 1;
Второй DFS обходит компоненту, состоящую только из чётных вершин:
cppdfs_ch(i);
При встрече соседней нечётной вершины выполняется:
cppk++;
То есть k становится количеством нечётных вершин, соединённых с этой чётной компонентой.
Можно выбрать любые две такие нечётные вершины. Путь между ними будет выглядеть так:
textнечётная — чётная — чётная — ... — чётная — нечётная
Все внутренние вершины принадлежат рассматриваемой компоненте и имеют чётные степени.
Количество способов выбрать два конца:
Поэтому нужно прибавлять:
cppans += 1LL * k * (k - 1) / 2;
У вас было:
cppans = k * (k - 1) / 2;
Здесь вы полностью затирали всё, что уже было посчитано:
Например, пусть первая чётная компонента дала 3 пути, а вторая дала 6. Правильный результат должен увеличиться на:
Ваш код сначала делал:
cppans = 3;
а потом:
cppans = 6;
В результате оставался только ответ для последней компоненты.
Исправление:
cppans += 1LL * k * (k - 1) / 2;
intДаже после замены = на += выражение:
cppk * (k - 1)
вычислялось бы как int, потому что обе переменные имеют тип int.
При k = 200000:
что значительно больше максимального значения int:
Переполнение происходит до присваивания в long long.
Поэтому нужен множитель 1LL:
cpp1LL * k * (k - 1) / 2
Теперь всё выражение вычисляется как long long.
Ваши функции могли вызвать рекурсию глубиной до 200000.
Например, для дерева-цепочки:
text1 — 2 — 3 — ... — 200000
глубина DFS может быть линейной. Стандартного стека программы для такого количества вызовов часто недостаточно, поэтому программа может завершиться с ошибкой выполнения.
Итеративный DFS через:
cppvector<int> q;
использует динамическую память и безопасно работает при заданных ограничениях.
Может показаться, что одна нечётная вершина способна иметь несколько рёбер в одну компоненту чётных вершин, из-за чего k увеличится несколько раз.
В дереве это невозможно.
Если нечётная вершина была бы соединена с двумя вершинами одной чётной компоненты, то внутри компоненты существовал бы путь между этими двумя вершинами. Вместе с двумя рёбрами к нечётной вершине получился бы цикл.
Но исходный граф является деревом, поэтому циклов в нём нет.
Следовательно, каждая пограничная нечётная вершина учитывается ровно один раз.
Итоговая сложность для одного теста:
по времени и
по памяти. С учётом ограничения на сумму n решение укладывается в заданные пределы.
Ограничение времени: 3 с
Ограничение реального времени: 6 с
Ограничение памяти: 256M
Задача H: Река
Условие задачи (pdf)
Во Флатландии протекает богатая рыбой река Большой Флат. Много лет назад река была поделена между n рыболовными предприятиями, каждое из которых получило непрерывный отрезок реки. При этом i-е предприятие, если рассматривать их по порядку, начиная от истока, изначально получило отрезок реки длиной ai.
С тех пор с рыболовными предприятиями во Флатландии k раз происходили различные события. Каждое из событий было одного из двух типов: банкротство некоторого предприятия или разделение некоторого предприятия на два. При некоторых событиях отрезок реки, принадлежащий предприятию, с которым это событие происходит, делится на две части. Каждый такой отрезок имеет длину большую или равную 2. Деление происходит по следующему правилу. Если отрезок имеет четную длину, то он делится на две равные части. Иначе он делится на две части, длины которых различаются ровно на единицу, при этом часть, которая ближе к истоку реки, имеет меньшую длину.
При банкротстве предприятия происходит следующее. Отрезок реки, принадлежавший обанкротившемуся предприятию, переходит к его соседям. Если у обанкротившегося предприятия один сосед, то этому соседу целиком передается отрезок реки обанкротившегося предприятия. Если же соседей двое, то отрезок реки делится на две части описанным выше способом, после чего каждый из соседей присоединяет к своему отрезку ближайшую к нему часть. При разделении предприятия отрезок реки, принадлежавший разделяемому предприятию, всегда делится на две части описанным выше способом. Разделившееся предприятие ликвидируется, и образуются два новых предприятия. Таким образом, после каждого события каждое предприятие владеет некоторым отрезком реки.
Министерство финансов Флатландии предлагает ввести налог на рыболовные предприятия, пропорциональный квадрату длины отрезка реки, принадлежащего соответствующему предприятию. Чтобы проанализировать, как будет работать этот налог, министр хочет по имеющимся данным узнать, как изменялась величина, равная сумме квадратов длин отрезков реки, принадлежащих предприятиям, после каждого произошедшего события.
Требуется написать программу, которая по заданному начальному разделению реки между предприятиями и списку событий, происходивших с предприятиями, определит, чему равна сумма квадратов длин отрезков реки, принадлежащих предприятиям, в начальный момент времени и после каждого события.
Формат входных данных
Первая строка входного файла содержит два целых числа: n и p — исходное количество предприятий (2⩽n⩽100000) и номер подзадачи (0⩽p⩽4) (считайте его просто так).
Вторая строка входного файла содержит n целых чисел a1, a2, …, an — длины исходных отрезков реки (1≤ai≤104).
Третья строка входного файла содержит целое число k — количество событий, происходивших с предприятиями (1⩽k⩽100000).
Последующие k строк содержат описания событий, i-я строка содержит два целых числа: ei и vi — тип события и номер предприятия, с которым оно произошло. Значение ei = 1 означает, что предприятие, которое после всех предыдущих событий является vi-м по порядку, если считать с единицы от истока реки, обанкротилось, а значение ei = 2 означает, что это предприятие разделилось на два.
Гарантируется, что значение vi не превышает текущее количество предприятий. Гарантируется, что если отрезок предприятия при банкротстве или разделении требуется поделить на две части, то он имеет длину большую или равную 2. Гарантируется, что если на реке осталось единственное предприятие, оно не банкротится.
Формат выходных данных
Выходной файл должен содержать (k+1) целых чисел, по одному в строке. Первая строка должна содержать исходную сумму квадратов длин отрезков реки, а каждая из последующих k строк — сумму квадратов длин отрезков реки после очередного события.
#include <iostream>
#include <chrono>
#include <random>
using namespace std;
using ll = long long;
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
struct Node {
int key, prior;
ll len, sum;
int left, right;
};
const int MAXN = 100005;
Node t[MAXN];
int root = 0;
int node_cnt = 0;
ll get_key(int v) {
return v ? t[v].key : 0;
}
ll get_len(int v) {
return v ? t[v].len : 0;
}
ll get_sum(int v) {
return v ? t[v].sum : 0;
}
void upd(int v) {
t[v].key = get_key(t[v].left) + get_key(t[v].right) + 1;
t[v].sum = get_sum(t[v].left) + get_sum(t[v].right) + get_len(v) * get_len(v);
}
void split(int v, int key, int &l, int &r) {
if (!v) {
l = r = 0;
return;
}
textint l_sz = get_key(t[v].left); if (l_sz < key) { split(t[v].right, key - l_sz - 1, t[v].right, r); l = v; } else { split(t[v].left, key, l, t[v].left); r = v; } upd(v);
}
ll merge(int l, int r) {
if (!l || !r) {
return l ? l : r;
}
textif (t[l].prior > t[r].prior) { t[l].right = merge(t[l].right, r); upd(l); return l; } else { t[r].left = merge(l, t[r].left); upd(r); return r; }
}
void bank(int key) {
int l, m1, m, m2, r;
split(root, key - 2, l, m1);
split(m1, 1, m1, m);
split(m, 1, m, m2);
split(m2, 1, m2, r);
textif (m1 && m2) { int k1 = t[m].len / 2; int k2 = (t[m].len + 1) / 2; t[m1].len += k1; t[m1].sum = t[m1].len * t[m1].len; t[m2].len += k2; t[m2].sum = t[m2].len * t[m2].len; } else if (m1) { t[m1].len += t[m].len; t[m1].sum = t[m1].len * t[m1].len; } else { t[m2].len += t[m].len; t[m2].sum = t[m2].len * t[m2].len; } root = merge(merge(l, merge(m1, m2)), r);
}
void del(int key) {
int l, m, r;
split(root, key - 1, l, m);
split(m, 1, m, r);
textll p1 = (t[m].len + 1) / 2; ll p2 = t[m].len / 2; int v = ++node_cnt; t[v].key = 1; t[v].prior = rng(); t[v].len = p1; t[v].sum = p1 * p1; t[v].left = t[v].right = 0; t[m].len = p2; t[m].sum = p2 * p2; root = merge(l, merge(merge(m, v), r));
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
textint n, p; cin >> n >> p; for (int i = 0; i < n; ++i) { int a; cin >> a; int v = ++node_cnt; t[v].key = 1; t[v].prior = rng(); t[v].len = a; t[v].sum = a * a; t[v].left = t[v].right = 0; root = merge(root, v); } int k; cin >> k; cout << t[root].sum << '\n'; while (k--) { int e, v; cin >> e >> v; if (e == 1) { bank(v); } else { del(v); } cout << t[root].sum << '\n'; }
}
что не так? сначала намекни, а потом исправь код, МИНИМАЛЬНО его изменяя и ОЧЕНЬ подробно все объясняя
Посмотрите не на логику split/merge, а на максимальное количество вершин декартова дерева.
Изначально их n, но каждое событие второго типа создаёт ещё одну вершину. Сравните возможное значение n + k с размером массива:
cppconst int MAXN = 100005; Node t[MAXN];
Также обратите внимание на тип результата функции merge: она возвращает номер вершины, а не величину типа long long.
Изменения минимальны:
cpp#include <iostream> #include <chrono> #include <random> using namespace std; using ll = long long; mt19937 rng(chrono::steady_clock::now().time_since_epoch().count()); struct Node { int key, prior; ll len, sum; int left, right; }; const int MAXN = 200005; Node t[MAXN]; int root = 0; int node_cnt = 0; int get_key(int v) { return v ? t[v].key : 0; } ll get_len(int v) { return v ? t[v].len : 0; } ll get_sum(int v) { return v ? t[v].sum : 0; } void upd(int v) { t[v].key = get_key(t[v].left) + get_key(t[v].right) + 1; t[v].sum = get_sum(t[v].left) + get_sum(t[v].right) + get_len(v) * get_len(v); } void split(int v, int key, int &l, int &r) { if (!v) { l = r = 0; return; } int l_sz = get_key(t[v].left); if (l_sz < key) { split(t[v].right, key - l_sz - 1, t[v].right, r); l = v; } else { split(t[v].left, key, l, t[v].left); r = v; } upd(v); } int merge(int l, int r) { if (!l || !r) { return l ? l : r; } if (t[l].prior > t[r].prior) { t[l].right = merge(t[l].right, r); upd(l); return l; } else { t[r].left = merge(l, t[r].left); upd(r); return r; } } void bank(int key) { int l, m1, m, m2, r; split(root, key - 2, l, m1); split(m1, 1, m1, m); split(m, 1, m, m2); split(m2, 1, m2, r); if (m1 && m2) { ll k1 = t[m].len / 2; ll k2 = (t[m].len + 1) / 2; t[m1].len += k1; t[m1].sum = t[m1].len * t[m1].len; t[m2].len += k2; t[m2].sum = t[m2].len * t[m2].len; } else if (m1) { t[m1].len += t[m].len; t[m1].sum = t[m1].len * t[m1].len; } else { t[m2].len += t[m].len; t[m2].sum = t[m2].len * t[m2].len; } root = merge(merge(l, merge(m1, m2)), r); } void del(int key) { int l, m, r; split(root, key - 1, l, m); split(m, 1, m, r); ll p1 = (t[m].len + 1) / 2; ll p2 = t[m].len / 2; int v = ++node_cnt; t[v].key = 1; t[v].prior = rng(); t[v].len = p1; t[v].sum = p1 * p1; t[v].left = t[v].right = 0; t[m].len = p2; t[m].sum = p2 * p2; root = merge(l, merge(merge(m, v), r)); } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int n, p; cin >> n >> p; for (int i = 0; i < n; ++i) { int a; cin >> a; int v = ++node_cnt; t[v].key = 1; t[v].prior = rng(); t[v].len = a; t[v].sum = 1LL * a * a; t[v].left = t[v].right = 0; root = merge(root, v); } int k; cin >> k; cout << t[root].sum << '\n'; while (k--) { int e, v; cin >> e >> v; if (e == 1) { bank(v); } else { del(v); } cout << t[root].sum << '\n'; } }
У вас было:
cppconst int MAXN = 100005; Node t[MAXN];
Изначально декартово дерево действительно содержит n вершин, по одной вершине на каждое предприятие.
Но при разделении предприятия выполняется:
cppint v = ++node_cnt;
То есть появляется новая вершина декартова дерева.
При этом старая вершина m не удаляется: она начинает представлять левое предприятие, а новая вершина v представляет правое предприятие.
Следовательно, после каждого события второго типа количество когда-либо созданных вершин увеличивается на один.
Максимально возможно:
и все k событий могут быть разделениями. Тогда будет создано:
вершин.
Ваш массив рассчитан только примерно на 100000:
cppNode t[100005];
Когда node_cnt станет больше 100004, код начнёт записывать за пределы массива:
cppint v = ++node_cnt; t[v].key = 1;
Это неопределённое поведение. Возможны разные последствия:
Исправление:
cppconst int MAXN = 200005;
Поскольку:
Индекс 0 используется как фиктивная пустая вершина, а реальные вершины имеют номера от 1 до 200000, поэтому размер 200005 достаточен.
mergeУ вас было:
cppll merge(int l, int r)
Но функция возвращает не сумму, не длину и не другое большое число. Она возвращает индекс корня декартова дерева:
cppreturn l; return r;
Индексы имеют тип int, потому что:
cppint root; int left, right;
Поэтому правильная сигнатура:
cppint merge(int l, int r)
В вашем варианте это обычно не приводило к неправильному ответу, поскольку возвращаемый long long затем неявно преобразовывался обратно в int:
cpproot = merge(...); t[l].right = merge(...);
Но тип был логически неверным. Кроме того, он скрывает смысл функции: merge возвращает номер корня объединённого дерева.
Это не основная причина падения, но это нужно исправить.
get_keyУ вас было:
cppll get_key(int v)
Однако key хранит размер поддерева:
cppint key;
Количество предприятий не превышает 200000, поэтому int более чем достаточен.
Исправлено на:
cppint get_key(int v)
Это также не было причиной неправильного ответа, но тип должен соответствовать хранимому значению.
bank корректнаРазберём ваши четыре операции split.
Пусть банкротится предприятие с номером key.
cppsplit(root, key - 2, l, m1);
После этого:
l содержит первые key - 2 предприятий;m1 содержит всё остальное, начиная с предприятия key - 1.Далее:
cppsplit(m1, 1, m1, m);
Теперь:
m1 — предприятие с номером key - 1, то есть левый сосед;m — всё начиная с банкротящегося предприятия.Далее:
cppsplit(m, 1, m, m2);
Теперь:
m — банкротящееся предприятие;m2 — всё справа от него.И наконец:
cppsplit(m2, 1, m2, r);
Теперь:
m2 — правый сосед;r — все предприятия после правого соседа.Получается структура:
textl | m1 | m | m2 | r
где:
m1 может отсутствовать, если банкротится первое предприятие;m2 может отсутствовать, если банкротится последнее предприятие;m всегда существует.Это разбиение корректно.
Если имеются оба соседа, вы делаете:
cppll k1 = t[m].len / 2; ll k2 = (t[m].len + 1) / 2;
Для чётной длины, например 8:
textk1 = 4 k2 = 4
Для нечётной длины, например 7:
textk1 = 3 k2 = 4
По условию часть, расположенная ближе к истоку, должна быть меньше.
Предприятия идут от истока слева направо, поэтому:
Ваш код делает именно это:
cppt[m1].len += k1; t[m2].len += k2;
То есть здесь ошибки нет.
sum у m1 и m2После последовательности split деревья m1 и m2 содержат ровно по одной вершине.
Например:
cppsplit(m1, 1, m1, m);
Левая часть после этого содержит ровно один элемент. Поэтому у корня m1 нет других элементов поддерева.
Его сумма равна просто квадрату его длины:
cppt[m1].sum = t[m1].len * t[m1].len;
Аналогично для m2.
После последующих вызовов merge функция upd пересчитает суммы всех предков:
cpproot = merge(merge(l, merge(m1, m2)), r);
Так что эта часть реализована правильно.
m можно просто не добавлять обратноПри банкротстве предприятие исчезает.
После распределения его участка вы собираете дерево без m:
cpproot = merge(merge(l, merge(m1, m2)), r);
Это правильно.
Память вершины m не переиспользуется, но это допустимо. Именно поэтому нужно хранить до n+k вершин, а не только текущее количество предприятий.
Количество активных предприятий может уменьшаться, но node_cnt никогда не уменьшается.
Например:
100000 начальных вершин.50000 разделений — создано ещё 50000.Удалённые узлы остаются в массиве как неиспользуемые, поэтому размер массива определяется общим числом созданий, а не максимальным текущим количеством предприятий.
При событии второго типа:
cppsplit(root, key - 1, l, m); split(m, 1, m, r);
получается:
textl | m | r
где m — разделяемое предприятие.
Вы вычисляете:
cppll p1 = (t[m].len + 1) / 2; ll p2 = t[m].len / 2;
Для нечётной длины, например 7:
textp1 = 4 p2 = 3
После этого:
cppt[m].len = p2; t[v].len = p1;
И собираете:
cppmerge(m, v)
То есть старый узел m находится левее нового узла v.
Следовательно:
m, его длина p2 = 3;v, его длина p1 = 4.Это полностью соответствует условию: меньшая часть должна находиться ближе к истоку.
Названия p1 и p2 немного сбивают с толку, но сама логика правильная.
1LL * a * aВ начальном заполнении у вас было:
cppt[v].sum = a * a;
Согласно ограничениям:
поэтому:
и конкретно здесь переполнения int не произойдёт.
То есть исходная строка была безопасна.
Тем не менее я заменил её на:
cppt[v].sum = 1LL * a * a;
Это подчёркивает, что квадрат вычисляется в long long, и защищает код при возможном изменении ограничений.
После объединений длина участка может быть намного больше 10^4, поэтому в остальных местах вы правильно используете ll len.
Максимальная суммарная длина реки:
Квадрат такой длины:
что уже требует long long, но ещё помещается в него.
long longПусть длины предприятий равны:
Суммарная длина реки постоянна:
Сумма квадратов максимальна, когда практически вся длина сосредоточена у одного предприятия:
long long хранит значения примерно до:
Следовательно, тип ll достаточен.
Операции неявного декартова дерева:
split;merge;splitработают в среднем за:
Каждое событие выполняет константное количество таких операций, поэтому общая ожидаемая сложность:
Память:
При ограничениях n, k ≤ 100000 это должно укладываться в лимиты.
Главная причина проблем вашего кода — запись за границы массива t при большом количестве разделений.