1802A - Лайки
Автор: Aleks5d, разработчик: vaaven
Давайте покажем конструкцию, которая максимизирует количество лайков. Нам нужно сначала оставить все лайки, которые мы можем поставить, а только потом убирать их.
Для того, чтобы минимизировать количество лайков, нам нужно убирать лайк (если мы можем) сразу после того как мы его поставили.
Приведенный ниже код реализует эти конструкции.
#include "bits/stdc++.h"
using namespace std;
void solve() {
int n;
cin >> n;
int likes = 0, dislikes = 0;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
if (x > 0) likes++;
else dislikes++;
}
for (int i = 1; i <= n; ++i) {
if (i <= likes) cout << i << ' ';
else cout << likes * 2 - i << ' ';
}
cout << '\n';
for (int i = 1; i <= n; ++i) {
if (i <= dislikes * 2) cout << i % 2 << ' ';
else cout << (i - dislikes * 2) << ' ';
}
cout << '\n';
}
signed main() {
int t = 1;
cin >> t;
for (int i = 1; i <= t; ++i) {
solve();
}
return 0;
}
1802B - Расселение морских свинок
Автор и разработчик: Aleks5d
Todo
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n;
cin >> n;
int known = 0, unknown = 0;
int need = 0;
for (int i = 0; i < n; ++i) {
int x;
cin >> x;
if (x == 1) ++unknown;
else {
known += unknown;
unknown = 0;
}
need = max(need, unknown + (known ? known / 2 + 1 : 0));
}
cout << need << endl;
}
signed main() {
int t = 1;
cin >> t;
for (int i = 0; i < t; ++i) {
solve();
}
return 0;
}
1801A - Очень красивое одеяло
Автор и разработчик: 4qqqq
Максимальное количество различных чисел, которые мы можем набрать, всегда равно $$$n \cdot m$$$. Покажем, как можно построить пример для любых $$$n$$$ и $$$m$$$.
Заметим, что если мы смогли построить корректную матрицу, то любая ее подматрица также является корректной матрицей меньшего размера. Поэтому давайте для некоторых $$$N$$$ и $$$M$$$ построим корректную матрицу, а в качестве ответа будем выводить левый верхний угол этой матрицы нужного размера.
Возьмем $$$N = M = 2^8$$$ и построим матрицу следующим алгоритмом. Разобьем её на блоки размера $$$2 \times 2$$$. Пронумеруем блоки слева направо и сверху вниз по порядку, начиная с нуля. $$$i$$$-й блок будет иметь вид
$$$4i + 0$$$ $$$4i + 1$$$
$$$4i + 2$$$ $$$4i + 3$$$
При таком построении побитовое исключающее ИЛИ любой подматрицы размера $$$2 \times 2$$$ будет равно нулю. Убедиться в этом можно следующим образом. Посмотрим на левый верхний угол $$$(i, \, j)$$$ произвольной подматрицы размера $$$2 \times 2$$$. Есть 4 случая:
- обе координаты четные;
- $$$i$$$ четная, $$$j$$$ нечетная;
- $$$i$$$ нечетная, $$$j$$$ четная;
- обе координаты нечетные.
Сразу отметим, что $$$i, \, j < 200 < 2^8$$$
Рассмотрим самый неприятный случай — последний. Остальные случаи рассматриваются аналогичным способом. В таком случае подматрица будет иметь вид:
$$$4i + 3$$$ $$$4(i + 1) + 2$$$
$$$4(i + 2^8) + 1$$$ $$$4(i + 2^8 + 1) + 0$$$
Заметим, что вторая часть каждого слагаемого меньше 4, а первая часть каждого слагаемого больше или равна 4. Поэтому их можно рассматривать независимо.
$$$3 \oplus 2 \oplus 1 \oplus 0$$$ $$$=$$$ $$$0$$$.
Если $$$i$$$ $$$=$$$ $$$1$$$, то
$$$4i \oplus 4(i + 1)$$$ $$$=$$$ $$$12$$$,
$$$4(1 + 2^8) \oplus 4(2 + 2^8)$$$ $$$=$$$ $$$12$$$.
Если $$$i \neq 1$$$, то
$$$4i \oplus 4(i + 1)$$$ $$$=$$$ $$$4$$$
$$$4(i + 2^8) \oplus 4(i + 2^8 + 1)$$$ $$$=$$$ $$$4$$$ (при $$$i=0$$$ можно проверить руками, при $$$1 < i < 2^8$$$ $$$4(i + 2^8)$$$ сократятся и от второго слагаемого останется $$$4$$$).
$$$4 \oplus 4 \oplus 0$$$ $$$=$$$ $$$0$$$. Таким образом, в выбранной подматрице побитовое исключающее ИЛИ равно нулю.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int SZ = 256;
int v[SZ][SZ];
void Solve(){
int n, m;
cin >> n >> m;
cout << n * m << '\n';
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
cout << v[i][j] << " \n"[j + 1 == m];
}
signed main(){
ios_base::sync_with_stdio(NULL);
cin.tie(NULL);
cout.tie(NULL);
{
int now = 0;
int n = 256;
int m = 256;
for(int i = 0; i < n; i += 2)
for(int j = 0; j < m; j += 2){
v[i][j] = now;
v[i][j + 1] = now + 1;
v[i + 1][j] = now + 2;
v[i + 1][j + 1] = now + 3;
now += 4;
}
}
int num_test = 1;
cin >> num_test;
for(int i = 1; i <= num_test; i++){
Solve();
}
}
1801B - Покупка подарков
Автор: Tikhon228, Разработчик: DishonoredRighteous
Для начала отсортируем все отделы по убыванию $$$b_i$$$ (а при равенстве~--- по возрастанию $$$a_i$$$). Теперь переберем отдел $$$i$$$, в котором будет куплен самый дорогой подарок для второй подруги. Заметим, что во всех отделах с номерами $$$j < i$$$, Саша должен купить подарок для первой подруги, иначе подарок $$$i$$$ не будет обладать максимальной стоимостью среди подарков, купленных для второй подруги. Поэтому сразу найдем значение $$$m = \max \limits_{j < i} a_j$$$. Таким образом, мы уже можем получить ответ $$$\lvert m - b_i \rvert$$$.
Во всех отделах с номерами $$$j > i$$$, для которых $$$a_j \le m$$$, Саша может купить подарок для любой из подруг, и это никак не повлияет на ответ. Теперь рассмотрим все отделы с номерами $$$j > i$$$, для которых $$$a_j > m$$$. Если в некотором из подобных отделов купить подарок для первой подруги, то значение $$$m$$$ увеличится, а значит ответ может улучшиться. Поэтому переберем все таким отделы и обновим ответ значением $$$\lvert a_j - b_i \rvert$$$.
Время работы: $$$O(n^2)$$$.
Давайте оптимизируем это решение. Для начала вместо того, чтобы вычислять величину $$$m$$$ на каждой итерации заново, будем поддерживать ее значение в некоторой переменной. Тогда при переходе от отдела $$$i - 1$$$ к отделу $$$i$$$ будем обновлять значение $$$m$$$ следующим образом: $$$m := \max(m, a_i)$$$.
Осталось научиться быстро находить оптимальный номер отдела $$$j$$$, такой что $$$j > i$$$, $$$a_j > m$$$, а также $$$\lvert a_j - b_i \rvert$$$ минимально. Выберем на суффиксе массива минимальное $$$a_j$$$, такое что $$$a_j \ge b_i$$$, а также максимальное $$$a_j$$$, такое что $$$a_j \le b_i$$$. Можно заметить, что оптимальным $$$a_j$$$ является одно из двух выбранных чисел (нужно также не забыть проверить условие $$$a_j > m$$$). Поэтому, достаточно обновить ответ только при помощи них.
Искать данные два элемента можно, используя структуру данных \texttt{set}. Будем поддерживать в множестве все $$$a_j$$$, находящиеся на суффиксе. Тогда можно найти нужные два элемента за $$$O(\log n)$$$. При переходе от отдела $$$i - 1$$$ к отделу $$$i$$$ нужно удалить значение $$$a_{i - 1}$$$ из структуры данных.
Время работы $$$O(n \log n)$$$
#include <bits/stdc++.h>
using namespace std;
template<typename T>
bool smin(T& a, const T& b) {
if (b < a) {
a = b;
return true;
}
return false;
}
template<typename T>
bool smax(T& a, const T& b) {
if (a < b) {
a = b;
return true;
}
return false;
}
const int INF = 0x3f3f3f3f;
const int N = 500100;
std::pair<int, int> a[N];
void run() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; ++i) {
scanf("%d%d", &a[i].first, &a[i].second);
}
sort(a, a + n, [&](const pair<int, int>& p1,
const pair<int, int>& p2) {
return p1.second > p2.second || (p1.second == p2.second && p1.first < p2.first);
});
multiset<int> setik;
for (int i = 0; i < n; ++i) {
setik.insert(a[i].first);
}
int mx = -INF;
int ans = INF;
for (int i = 1; i < n; ++i) {
smin(ans, abs(a[i].first - a[0].second));
}
for (int i = 0; i < n; ++i) {
setik.erase(setik.find(a[i].first));
if (i == 0) {
mx = a[i].first;
continue;
}
smin(ans, abs(mx - a[i].second));
auto it = setik.lower_bound(a[i].second);
if (it != setik.end() && *it >= mx) {
smin(ans, abs(*it - a[i].second));
}
if (it != setik.begin() && *std::prev(it) >= mx) {
smin(ans, abs(*prev(it) - a[i].second));
}
smax(mx, a[i].first);
}
printf("%d\n", ans);
}
int main(void) {
int t;
scanf("%d", &t);
while (t--) {
run();
}
return 0;
}
1801C - Музыкальный фестиваль
Автор: vaaven, Разработчик: ViktorSM
Введём для альбома понятие сжатого альбома , который получается из исходного путём удаления всех элементов, кроме тех, которые являются первыми максимумами на соответствующих им префиксах.
К примеру:
Для альбома $$$[\textbf{1}, \textbf{4}, 4, 3, \textbf{6}, 5, 6]$$$ сжатым будет альбом $$$[1, 4, 6]$$$.
Теперь заметим, что решение исходной задачи сводится к решению этой же задачи, но на сжатых альбомах. Действительно, ответ на них не будет отличаться, ведь если какой-то элемент увеличивал впечатление на обычных альбомах, то он будет увеличивать, если сжать альбомы и наоборот. Далее будет считаться, что предварительно все альбомы были сжаты.
Введём $$$dp_c$$$ — максимум впечатления, которое можно получить, если бы не было альбомов, таких, что в них есть элементы большие, чем $$$c$$$. Тогда, $$$dp_c$$$ равно $$$dp_{c-1}$$$, либо можно добавить ещё элемент или два, если в $$$c$$$ является максимальным элементом для какого-то альбом. Тогда для всех сжатых альбомов, можно пересчитаться через значение $$$dp$$$ в точке перед первым элементом альбома, либо через $$$c - 1$$$. Таким образом для пересчёта достаточно знать для каждого $$$c$$$ какие альбомы закончились в этом индексе, а так же для каждого альбома его первый элемент. Решение за $$$O(K)$$$
Давайте теперь решим полную задачу. Для каждого значения $$$c$$$ запомним индексы альбомов, которые содержат элемент равный $$$c$$$. Идём в порядке увеличения $$$c$$$, поддерживаем для каждого альбома значение $$$dp_i$$$ — максимум впечатления, который можно получить если бы не было элементов больших $$$c$$$ и Маша послушала последним $$$i$$$-й альбом. Пусть для очередного $$$c$$$ существует альбом $$$i$$$, что в нём есть песня с крутостью $$$c$$$. Тогда $$$dp_i$$$ надо взять максимумом из $$$dp_i + 1$$$ и значения по всем $$$dp_j + 1$$$, таких, что максимальный элемент в $$$j$$$-м альбоме меньше чем максимальный элемент $$$i$$$-го, так как она могла послушать этот трек, либо следующим в этом альбоме, либо после того, как послушала какой-то другой альбом полностью. Заметим, что можно хранить значение $$$mx$$$ — максимум по всем альбомам, для которых максимальное значение в них меньше $$$c$$$ и пересчитывать его при переходе к $$$c + 1$$$, храня те альбомы, которые закончились, тогда получится решение за $$$O(K + C)$$$.
#include "bits/stdc++.h"
#include <algorithm>
#include <locale>
#include <random>
#include <unordered_map>
#include <vector>
using namespace std;
#define all(x) x.begin(), x.end()
#define rall(x) x.rbegin(), x.rend()
typedef long long ll;
typedef long double db;
typedef unsigned long long ull;
vector<int> shrink(vector<int> &a) {
vector<int> a1;
int n = a.size();
int mx = 0;
for (int i = 0; i < n; ++i) {
if (a[i] > mx) {
a1.emplace_back(a[i]);
mx = a[i];
}
}
return a1;
}
void solve() {
int n;
cin >> n;
vector<vector<int>> a(n);
int k;
for (int i = 0; i < n; ++i) {
int k;
cin >> k;
a[i].resize(k);
for (auto &j : a[i]) {
cin >> j;
}
}
vector<vector<int>> a1(n);
for (int i = 0; i < n; ++i) {
a1[i] = shrink(a[i]);
}
map<int, vector<int>> b;
for (int i = 0; i < n; ++i) {
for (auto &j : a1[i]) {
b[j].emplace_back(i);
}
}
vector<int> dp(n);
int closed = 0;
for (auto &it : b) {
int c = it.first;
int newclosed = 0;
for (auto &i : it.second) {
if (c == a1[i].back()) {
dp[i] = max(dp[i] + 1, closed + 1);
newclosed = max(newclosed, dp[i]);
continue;
}
if (c == a1[i].front()) {
dp[i] = closed + 1;
continue;
}
dp[i] = max(dp[i] + 1, closed + 1);
}
closed = max(closed, newclosed);
}
cout << *max_element(all(dp));
}
signed main() {
int t = 0;
cin >> t;
while (t --> 0) {
solve();
cout << '\n';
}
return 0;
}
1801D - Путь домой
Автор: Tikhon228, Разработчик: Ormlis
Заметим, что шоу можно делать "отложенно". Как только нам стало не хватать денег, чтобы пройти по ребру, можно сделать несколько шоу заранее среди вершин, которые мы уже прошли, так, чтобы заработать максимальное число денег.
Для общего случая можно написать $$$dp[v][best] = (\textit{min show}, \textit{max money})$$$, где $$$v$$$ — номер вершины где мы находимся, а $$$best$$$ — вершина с макс. $$$w_i$$$, через которую мы уже прошли. Можно показать, что оптимально сначала минимизировать количество шоу, а потом максимизировать количество денег. Эту динамику можно пересчитывать с помощью алгоритма Дейкстры. Асимптотика $$$O(mn \log n)$$$
#include "bits/stdc++.h"
#define rep(i, n) for (int i = 0; i < (n); ++i)
#define pb push_back
#define all(a) (a).begin(), (a).end()
#define ar array
#define vec vector
using namespace std;
using ll = long long;
using pi = pair<int, int>;
using vi = vector<int>;
using vpi = vector<pair<int, int>>;
const ll INF = 2e18;
const int maxN = 3e5 + 10;
struct PathParams {
ll num_show;
int money;
};
bool operator==(const PathParams &a, const PathParams &b) {
return tie(a.num_show, a.money) == tie(b.num_show, b.money);
}
bool operator!=(const PathParams &a, const PathParams &b) {
return !(a == b);
}
struct State {
PathParams params;
int v;
int best;
};
bool operator<(const PathParams &a, const PathParams &b) {
if (a.num_show != b.num_show) return a.num_show < b.num_show;
return a.money > b.money;
}
bool operator<(const State &a, const State &b) {
return tie(a.params, a.v, a.best) < tie(b.params, b.v, b.best);
}
bool operator>(const State &a, const State &b) {
return !(a < b);
}
void solve() {
int n, m, p, group;
cin >> n >> m >> p;
vector dp(n, vector<PathParams>(n, {INF, 0}));
vector<vpi> g(n);
vi w(n);
rep(i, n) cin >> w[i];
rep(i, m) {
int a, b, s;
cin >> a >> b >> s;
a--;
b--;
g[a].emplace_back(b, s);
}
dp[0][0] = {0, p};
priority_queue<State, vector<State>, greater<>> pq;
pq.push({.params = {.num_show=0, .money=p}, .v = 0, .best=0});
while (!pq.empty()) {
auto current = pq.top();
pq.pop();
int v = current.v;
int best = current.best;
if (dp[v][best] != current.params) continue;
for (auto &[u, s]: g[v]) {
auto state2 = current;
PathParams &path = state2.params;
if (path.money < s) {
ll need = (s - path.money + w[best] - 1) / w[best];
path.num_show += need;
path.money += need * w[best];
assert(path.money < s + w[best]);
}
path.money -= s;
state2.v = u;
if (w[u] > w[state2.best]) state2.best = u;
if (path < dp[u][state2.best]) {
dp[u][state2.best] = path;
pq.push(state2);
}
}
}
ll ans = INF;
rep(i, n) {
ans = min(ans, dp[n - 1][i].num_show);
}
cout << (ans == INF ? -1 : ans) << '\n';
}
signed main() {
int t = 1;
cin >> t;
rep(_, t) {
solve();
}
return 0;
}
1801E - Цены на бензин
Автор и разработчик: Kirill22
Для начала поймём, что от нас требуется. Дано дерево, в каждой вершине которого записан диапазон цен для этой вершины. Запрос это пара путей равной длины, цены на $$$i$$$-ых вершинах вдоль этих путей должны быть равны для всех $$$i$$$. Нужно найти количество способов расставить цены на вершинах для каждого префикса ограничений
Начнём с медленного решения задачи. Будем хранить компоненты связности(в каждой цены на вершинах должны быть равны). Для каждой из них храним допустимый диапазон цен. Ответом будет произведение длин диапазонов по всем компонентам. Будем идти вдоль путей и объединять 2 вершины в одну компоненту с помощью СНМ. Понятно, что для ускорения этого решения надо быстрее искать моменты, когда две вершины объединяются в одну компоненту.
Сначала разберём долгое решение. Сделаем heavy-light decomposition, с помощью которого будем хешировать пути в дереве, приняв за символ для вершины номер корня её компоненты. Теперь с помощью бинпоиска будем искать первый момент, когда хеши на префиксах путей различаются, то есть две вершины объединяются в одну компоненту. С помощью переливаний обновим для вершин корни их компонент и дерево отрезков для hld. Получим $$$n$$$ объединений, каждое найдём за $$$O(log^2(n))$$$ с помощью hld. Также будет $$$O(n \cdot log(n))$$$ обновлений в дереве отрезков из-за переливаний. На каждый запрос будет $$$O(log^2(n))$$$ от hld. Итоговая асимптотика $$$O((n + q) \cdot log^2(n))$$$
Теперь приведём красивое решение данной задачи. Начнём с бамбука.
Заменим равенство цен на паре путей на две пары путей с длинами равными максимальной степени двойки, меньшей длины исходного пути (как в sparse table). Теперь длины путей всех ограничений стали степеням двойки. Будем перебирать степени двойки в порядке убывания $$$2^k$$$, для каждого пути длины $$$2^k$$$ заведём вершину в графе, также заведём вершину для каждого такого пути в обратном порядке. Теперь ограничения задают рёбра в таком графе. Проведём их, выделим остовное дерево. Для каждого ребра из остова разделим ограничения на 2 ограничения с длинами путей в два раза меньше и продолжим процесс. На слое с длинами 1 получим нужное нам остовное дерево, которое будет отвечать за первые моменты, когда некоторые пары вершин объединялись в компоненты. Заметим, что с каждого слоя добавится вниз не более $$$2n$$$ рёбер, а также от запросов не более $$$2q$$$ рёбер. То есть каждый слой отработает за $$$O((n + q) \cdot \alpha(n))$$$, где $$$\alpha(n)$$$ — среднее время работы в снм, обратная функция Аккермана. Получили решение за $$$O((n + q) \cdot \alpha(n) \cdot log(n))$$$
Для полного решения на дереве для начала разобьём пару путей на три пары соответствующих вертикальных путей(возьмём из 4 конечных вершин этих путей самую близкую к lca пары вершин на её пути, тогда в пару к этому пути поставим вертикальный путь(часть другого пути), теперь получим один вертикальный путь и произвольный путь в дереве, разобьём второй путь по lca и первый по соответствующим длинам). Далее поступим аналогично бамбуку, только место вершины, отвечающей за отрезок, заведём вершину отвечающую за бинарный подъём в дереве на высоту равную степени двойки.
#include "bits/stdc++.h"
using namespace std;
const int mod = (int) 1e9 + 7;
int inv(int x) {
int res = 1, n = mod - 2;
while (n) {
if (n & 1) {
res = res * 1ll * x % mod;
}
x = x * 1ll * x % mod;
n /= 2;
}
return res;
}
const int N = (int) 2e5 + 22, K = 18;
vector<int> g[N];
pair<int, int> a[N];
int n, q, ans = 1;
int h[N], up[N][K], lg[N];
vector<array<int, 3>> graph[K]; // lower vertex1, lower vertex2, time, vertex with direction
vector<array<int, 3>> gr[N];
vector<array<int, 3>> edges;
struct dsu {
vector<int> p, sz;
void build(int n) {
p.resize(n);
sz.resize(n);
for (int i = 0; i < n; i++) {
p[i] = i;
sz[i] = 1;
}
}
int get(int v) {
return (v == p[v] ? v : p[v] = get(p[v]));
}
bool merge(int v, int u) {
v = get(v), u = get(u);
if (v != u) {
if (sz[v] > sz[u]) {
swap(v, u);
}
p[v] = u;
sz[u] += sz[v];
return true;
}
return false;
}
} G, dsu[K];
void dfs(int v, int pr, int d) {
h[v] = d;
up[v][0] = pr;
for (int j = 1; j < K; j++) {
up[v][j] = up[up[v][j - 1]][j - 1];
}
for (auto u : g[v]) {
dfs(u, v, d + 1);
}
}
int la(int v, int x) {
for (int j = 0; j < K; j++) {
if (x >> j & 1) {
v = up[v][j];
}
}
return v;
}
int lca(int v, int u) {
if (h[v] > h[u]) {
swap(v, u);
}
u = la(u, h[u] - h[v]);
if (v == u) {
return v;
}
for (int j = K - 1; j >= 0; j--) {
if (up[v][j] != up[u][j]) {
v = up[v][j], u = up[u][j];
}
}
return up[v][0];
}
int id(int v) {
return (v > 0 ? v : -v + n);
}
int sgn(int v) {
return (v > 0 ? 1 : -1);
}
void add_edge(int j, int v, int u, int t) {
if (dsu[j].merge(id(v), id(u))) {
if (j > 0) {
if (sgn(v) == sgn(u)) {
add_edge(j - 1, v, u, t);
add_edge(j - 1, sgn(v) * up[abs(v)][j - 1], sgn(u) * up[abs(u)][j - 1], t);
} else {
if (sgn(v) == -1) {
swap(v, u);
}
add_edge(j - 1, v, sgn(u) * up[abs(u)][j - 1], t);
add_edge(j - 1, sgn(v) * up[abs(v)][j - 1], u, t);
}
} else {
edges.push_back({abs(v), abs(u), t});
}
}
}
void add(int v, int u, int x, int y, int t, int type1, int type2) {
if (h[v] < h[u]) {
swap(v, u);
}
if (h[x] < h[y]) {
swap(x, y);
}
assert(h[v] - h[u] == h[x] - h[y]);
int g = lg[h[v] - h[u]];
if (type1 == type2) {
add_edge(g, type1 * v, type2 * x, t);
add_edge(g, type1 * la(v, h[v] - h[u] - (1 << g) + 1), type2 * la(x, h[x] - h[y] - (1 << g) + 1), t);
} else {
add_edge(g, type1 * v, type2 * la(x, h[x] - h[y] - (1 << g) + 1), t);
add_edge(g, type1 * la(v, h[v] - h[u] - (1 << g) + 1), type2 * x, t);
}
}
void merge(int v, int u) {
v = G.get(v), u = G.get(u);
if (v != u) {
G.merge(v, u);
if (G.sz[v] > G.sz[u]) {
swap(v, u);
}
if (a[v].first <= a[v].second) {
ans = ans * 1ll * inv(a[v].second - a[v].first + 1) % mod;
}
if (a[u].first <= a[u].second) {
ans = ans * 1ll * inv(a[u].second - a[u].first + 1) % mod;
}
a[u].first = max(a[u].first, a[v].first);
a[u].second = min(a[u].second, a[v].second);
if (a[u].first > a[u].second) {
ans = 0;
} else {
ans = ans * 1ll * (a[u].second - a[u].first + 1) % mod;
}
}
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
for (int j = 2; j < N; j++) {
lg[j] = lg[j / 2] + 1;
}
cin >> n;
for (int i = 2; i <= n; i++) {
int v;
cin >> v;
g[v].push_back(i);
}
for (int i = 1; i <= n; i++) {
cin >> a[i].first >> a[i].second;
ans = ans * 1ll * (a[i].second - a[i].first + 1) % mod;
}
dfs(1, 1, 0);
cin >> q;
for (int j = 0; j < K; j++) {
dsu[j].build(2 * n + 1);
}
for (int i = 0; i < q; i++) {
int v, u, x, y;
cin >> v >> u >> x >> y;
int w = lca(v, u);
int z = lca(x, y);
if (h[v] - h[w] > h[x] - h[z]) {
swap(v, x);
swap(u, y);
swap(w, z);
}
if (v != w) {
int d = h[v] - h[w];
int v2 = la(v, d - 1);
int x2 = la(x, d - 1);
add(v, v2, x, x2, i, 1, 1);
v = up[v2][0];
x = up[x2][0];
}
if (x != z) {
int d = h[x] - h[z];
int v2 = la(u, (h[u] - h[v]) - d);
int x2 = la(x, d - 1);
add(v, up[v2][0], x, x2, i, -1, 1);
v = v2;
x = up[x2][0];
}
add(v, u, x, y, i, (h[v] > h[u] ? 1 : -1), (h[x] > h[y] ? 1 : -1));
}
G.build(n + 1);
int j = 0;
for (int i = 0; i < q; i++) {
while (j < (int) edges.size() && edges[j][2] == i) {
merge(edges[j][0], edges[j][1]);
j++;
}
cout << ans << '\n';
}
}
1801F - Ещё одна n-мерная шоколадка
Автор и разработчик: Tikhon228
За $$$A$$$ обозначим максимальное значение $$$a_i$$$
Для начала решим задачу за $$$O(n \cdot k \cdot f(k, A))$$$ с помощью динамического программирования.
Положим $$$dp[i][j]$$$ — максимальный возможный объем наименьшего кусочка, если по первым $$$i$$$ измерениям мы разделили шоколадку на $$$j$$$ частей. Если мы разделили более чем на $$$k$$$ частей, так же положим результат в $$$dp[i][k]$$$. В пересчете нам нужно решить на сколько часей делить шоколадку вдоль очередного измерения. Рассмотрим несколько способов это сделать.
Можно за $$$O(k)$$$ перебрать состояние, в которое переходим, и из этого посчитать на сколько частей нужно разделить шоколадку вдоль очередного измерения. — Получим $$$O(n \cdot k^2)$$$
Можно за $$$O(A)$$$ перебрать на сколько частей мы делим шоколадку вдоль очередного измерения.
Находясь в состоянии $$$dp[i][j]$$$, можно перебирать $$$b_i$$$ — на сколько частей делить шоколадку, пока $$$j \cdot b_i \le k$$$. Можно показать, что такое решение будет работать за $$$O(n \cdot k \cdot \ln{k})$$$
Ключевая идея
предположим нам нужно разделить шоколадку на $$$10$$$ частей, и вдоль первых измерений мы уже разделили её на $$$5$$$ частей, или на $$$6$$$ частей, или на $$$7, 8$$$ или $$$9$$$ частей. Все эти состояния не различимы для нас, потому что во всех этих случаях нам нужно разделить шоколадку ещё хотя бы на $$$2$$$ части. Осталось понять сколько всего таких <<интересных>> состояний и научиться их хранить. Для этого есть несколько подходов, разберем один из них:
нам интересны все значения $$$\lceil \frac{k}{i} \rceil$$$ для $$$i = 1, 2, \ldots k$$$ — это то, на сколько частей ещё может быть нужно разделить шоколадку. Среди них всего $$$O(\sqrt{k})$$$ различных, так как либо $$$i \le \sqrt{k}$$$, либо само значение $$$\lceil \frac{k}{i} \rceil \le \sqrt{k}$$$. Если сделать все эти числа состояниями, и пересчитываться, перебирая состояние, в которое переходить, получим $$$O(n \cdot \sqrt{k} \cdot \sqrt{k}) = O(n \cdot k)$$$ — этого всё ещё не достаточно, чтобы решить полую задачу.
Последнее наблюдение
Если мы находимся в состоянии $$$dp[i][remain]$$$ где $$$remain = \lceil \frac{k}{i} \rceil$$$ для какого-то $$$i$$$ — применим к нему ту же идею. Из него нам интересны переходы в состояния $$$\lceil \frac{remain}{j} \rceil$$$ для $$$j = 1, 2, \ldots remain$$$. Какая асимптотика получится, если перебирать только интересные переходы?
$$$n \cdot (\sum\limits_{r=1}^{\sqrt{k}}{ 2 \cdot \sqrt{r} + 2 \cdot \sqrt{\lceil \frac{k}{r} \rceil}})$$$
- можно показать, что это $$$O(n \cdot k^{3/4})$$$ — что решает задачу
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200;
int a[MAXN];
const int MAXK = 1e7 + 100, MAXH = 1e4;
int hsh[MAXK];
int rev[MAXH];
double dp[MAXN][MAXH];
vector<array<int, 2>> go[MAXH];
main() {
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, k;
cin >> n >> k;
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
int id = 0;
for (int c = 1;; ++id) {
rev[id] = (k + c - 1) / c;
hsh[(k + c - 1) / c] = id;
int t = (k + c - 1) / c - 1;
if (t == 0) break;
c = (k + t - 1) / t;
}
++id;
dp[0][hsh[k]] = k;
for (int i = 0; i < id; ++i) {
int k = rev[i];
for (int c = 1;;) {
go[i].push_back({c, hsh[(k + c - 1) / c]});
int t = (k + c - 1) / c - 1;
if (t == 0) break;
c = (k + t - 1) / t;
}
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < id; ++j) {
double val = dp[i][j];
if (val == 0) continue;
for (auto elem : go[j]) {
int c = elem[0], k1 = elem[1];
if (c > a[i]) break;
int cur = a[i] / c;
dp[i + 1][k1] = max(dp[i + 1][k1], val * cur / a[i]);
}
}
}
cout << fixed << setprecision(20);
cout << dp[n][hsh[1]] << '\n';
return 0;
}
1801G - Задачечка на подстрочечки
Автор и разработчик: grphil
Воспользуемся структурой Ахо-Корасик для хранения строк из $$$S$$$. Построим на нём сжатые суффиксные ссылки. Так можно чуть оптимальнее найти все строки из $$$S$$$, заканчивающиеся в данной позиции $$$t$$$.
Обозначим за $$$pref[i]$$$ число подстрок из $$$S$$$ у префикса $$$t$$$ длины $$$i$$$.
Обозначим за $$$suf[i]$$$ число подстрок из $$$S$$$ у суффикса $$$t$$$ начиная с позиции $$$i$$$.
Заметим, что $$$pref[r] + suf[l] - pref[|t|]$$$ равно числу подстрок строки $$$t$$$ из $$$S$$$ на отрезке $$$[l, r]$$$ минус число подстрок $$$t$$$ из $$$S$$$, которые начинаются раньше $$$l$$$ и заканчиваются позже $$$r$$$.
Для каждого запроса найдём подстроку $$$t$$$, совпадающую с $$$s_i$$$, которая покрывает строку $$$t[l, r]$$$ и заканчивается как можно ближе к $$$r$$$. Если такой нет, то ответ можно посчитать по предыдущей формуле. Иначе $$$t[l, r]$$$ вкладывается в $$$s_i[l', r']$$$. При этом в строке $$$s_i$$$ нет подстрок из $$$S$$$, начинающихся раньше $$$l'$$$ и заканчивающихся позже $$$r'$$$. Для получения ответа применим прошлую формулу строкой $$$s_i$$$ и подотрезком $$$[l', r']$$$.
Асимптотика: $$$O(S + |t| + m \log m)$$$
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
struct node {
int nx[26];
int p;
int pp;
int len;
int id;
int cnt;
bool term;
node() : p(-1), pp(-1), len(0), id(-1), term(false), cnt(0) {
for (int i = 0; i < 26; i++) {
nx[i] = -1;
}
}
};
vector<node> g;
vector<string> s[2];
string t[2];
vector<vector<long long>> c[2], pid[2];
vector<long long> tc[2];
int add(int a, char c) {
c -= 'a';
if (g[a].nx[c] == -1) {
g[a].nx[c] = g.size();
g.emplace_back();
g.back().len = g[a].len + 1;
}
return g[a].nx[c];
}
void build_aho(int a) {
vector<pair<int, int>> q;
for (int i = 0; i < 26; i++) {
if (g[a].nx[i] == -1) {
g[a].nx[i] = a;
} else {
q.emplace_back(a, i);
}
}
int qb = 0;
while (qb < q.size()) {
int b = q[qb].x;
int i = q[qb].y;
qb++;
int v = g[b].nx[i];
int c = g[b].p;
if (g[v].term) {
g[v].cnt = 1;
}
if (c == -1) {
g[v].p = a;
g[b].pp = -1;
} else {
g[v].p = g[c].nx[i];
if (g[v].term) {
g[v].pp = v;
} else {
g[v].pp = g[g[v].p].pp;
}
g[v].cnt += g[g[v].p].cnt;
}
for (int i = 0; i < 26; i++) {
if (g[v].nx[i] == -1) {
g[v].nx[i] = g[g[v].p].nx[i];
} else {
q.emplace_back(v, i);
}
}
}
}
vector<vector<pair<int, int>>> ts;
vector<int> qlen;
priority_queue<pair<int, int>> h;
vector<long long> ans;
long long get_ans(int rdst, int len, vector<long long>& a, vector<long long>& b, bool substr) {
int l = a.size() - 1 - rdst;
int r = l + len;
long long cnt = a[r] + b[a.size() - 1 - l] - a.back();
if (substr && l == 0 && r == a.size() - 1) {
cnt++;
}
return cnt;
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
g.emplace_back(); g.emplace_back();
s[0].resize(n); s[1].resize(n);
c[0].resize(n); c[1].resize(n);
pid[0].resize(n); pid[1].resize(n);
ans.resize(m);
cin >> t[0];
t[1] = t[0];
reverse(t[1].begin(), t[1].end());
ts.resize(t[0].size());
for (int i = 0; i < n; i++) {
cin >> s[0][i];
}
sort(s[0].begin(), s[0].end(), \
[](const string& a, const string& b) { return a.size() < b.size(); });
for (int i = 0; i < n; i++) {
s[1][i] = s[0][i];
reverse(s[1][i].begin(), s[1][i].end());
for (int e = 0; e < 2; e++) {
int a = e;
for (auto j : s[e][i]) {
a = add(a, j);
}
g[a].term = true;
g[a].id = i;
}
}
build_aho(0); build_aho(1);
for (int e = 0; e < 2; e++) {
tc[e].resize(t[0].size() + 1);
int a = e;
for (int i = 0; i < t[0].size(); i++) {
a = g[a].nx[t[e][i] - 'a'];
tc[e][i + 1] = tc[e][i] + g[a].cnt;
}
for (int i = 0; i < n; i++) {;
c[e][i].resize(s[0][i].size() + 1);
pid[e][i].resize(s[0][i].size() + 1, -1);
int a = e;
for (int j = 0; j < s[e][i].size(); j++) {
a = g[a].nx[s[e][i][j] - 'a'];
c[e][i][j + 1] = c[e][i][j] + g[a].cnt;
if (g[a].term) {
pid[e][i][j + 1] = g[a].id;
}
}
for (int j = (int)s[e][i].size() - 1; j >= 0; j--) {
if (pid[e][i][j] == -1) {
pid[e][i][j] = pid[e][i][j + 1];
}
}
c[e][i].back()--;
}
}
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
a--;
ts[b - 1].emplace_back(a, i);
qlen.emplace_back(b - a);
}
int a = 0;
for (int i = 0; i < t[0].size(); i++) {
for (auto j : ts[i]) {
h.emplace(j);
}
a = g[a].nx[t[0][i] - 'a'];
if (g[a].pp != -1) {
int id = g[g[a].pp].id;
int bg = i + 1 - (int)s[0][id].size();
while (h.size() > 0 && h.top().x >= bg) {
int rdst = i - h.top().x + 1;
int nid = pid[1][id][rdst];
ans[h.top().y] = get_ans(rdst, qlen[h.top().y],
c[0][nid], c[1][nid], true);
h.pop();
}
}
}
while (h.size() > 0) {
ans[h.top().y] = get_ans(t[0].size() - h.top().x, qlen[h.top().y], tc[0], tc[1], false);
h.pop();
}
for (auto i : ans) {
cout << i << ' ';
}
cout << '\n';
}
Testcases aside, I thought this was generally a really cool round (or at least all the problems I tried).
same
What does SNM mean in tutorial of d1E?
It's DSU, fixed now
It can be shown that it is optimal to minimize the number of shows first, and then maximize the amount of money.
Can anyone share a proof of this statement? it seems intuitive but i failed to prove it:c
If there are fewer shows, they can do another show to get more money
It can be seen that for all paths with end vertex $$$v$$$ and best vertex $$$t$$$, the number of shows is $$$0$$$ and/or the amount of money $$$< w_t$$$. So comparing any two possible paths with the same $$$v, t$$$, you can always do more shows on the path with fewer shows to get at least $$$w_t$$$ rubles, which would be more money than the other path.
Never thought 69 would hurt so much
This round is a little difficult, but the tasks are really interesting. I love div 2C.
div1E smart solution is great!
Can I solve solve Div1 B with ternary search? If so, why my solution 197805657 did'nt work for this? Can someone help me?
alternate construction for beautiful blanket: for each row, simply use consecutive integers. denote the smallest power of 2 greater than m as 2^j. then let the first element in each row i be i*2^j, and the rest of the elements be simply the consecutive integers after i*2^j. this construction satisfies the conditions in the problem. examples:
Thanks ! Your solution is easier to prove its accuracy.
can any one say whats wrong with this solution for div2.c
Check the xor value of 3,6,13,16 (not zero)
为什么没有中文版?
Why is there no Chinese version?
I think it is necessary,too.
I'm sure this is inadvertant, but in problem 1801A, after submitting any wrong solution, the pattern is literally visible in jury's answer xD.
Can somebody explain solution to Div 1. C? I can't understand what the editorial says.
This is how I solved the problem. It's a bit long but I tried to make it as comprehensive as possible -
the first thing we notice is no matter which track we start from in a particular song, the ending track will always remain the same and this position is the last track in the song such that it is strictly greater than all tracks before it. Let's denote this track's value as $$$X[i]$$$.
We notice that if we place a song $$$j$$$ with larger $$$X[j]$$$ before a song $$$i$$$ all tracks in song $$$i$$$ become redundant since you can't continue any of them.
So it's best if we sort the songs by the value of $$$X[i]$$$ in non decreasing order. But the issue is when we reach a case like -
$$$n$$$ = 3
first song — 1 2 3
second song — 5
third song — 4 5 6
here we could place the third song after the first song to get a higher value.
From now on we shall consider all songs as sorted by $$$X[i]$$$. So we do dp, where $$$dp[i]$$$ stores the max coolness in the prefix of length $$$i$$$.
We also notice that the values of $$$dp[i]$$$ are non decreasing since we can always just continue off of the values of $$$dp[i-1]$$$.
Now, let us denote $$$val[i][j]$$$ as the max that we can extend the $$$jth$$$ track in song i, for example the following song would have values —
song = 4 1 3 6 2 8
$$$val[i]$$$ = 3 -1 -1 2 -1 1
I denote -1 for some $$$val[i][j]$$$ because they are not the max value in the prefix and they never add to the coolness.
To compute $$$dp[i]$$$, we go through all tracks of song $$$i$$$ and binary search to find the first $$$X[k]$$$ that is strictly less than $$$a[i][j]$$$ (where $$$a[i][j]$$$ denotes the value of track $$$j$$$ in song $$$i$$$) and $$$val[i][j] \neq -1$$$, then we choose the max value of $$$dp[k]+val[i][j]$$$ over all valid tracks $$$j$$$.
The final answer is $$$dp[n]$$$. Here is my submission
Thank you bro, great explanation!
I did pretty much the same thing that you did. I am somehow getting the wrong answer. Here is my submission link submission. I am having a hard time debugging. Could you please help
What's the correctness issue with using Dijkstra's directly using the same cost function as the editorial mentions, and using the shortest path found to n-1, rather than maintaining the dp state for NxN [v][best] pairs?
It might be optimal to 'stop by' a different node for higher money from performances, before continuing on to the end.
сетик в подарках))
The solution for 1801A can be simpler: just let M(i,j)=i*256+j.
I appreciate the fast editorial, however it has been quite a while and the solution for B has not been posted yet. Aleks5d please post the solution! It would help newbies like me out :)
Let U be a guinea pig whose gender is not yet known.
If $$$x=1$$$, let $$$U:=U+1$$$.
If $$$x=2$$$, let $$$K$$$ be the number of guinea pigs whose gender is known.
In the state of $$$U$$$, it must always be in a cage. For $$$K$$$, $$$⌈K/2⌉$$$ cages are needed.
This is because in the worst case, the gender must be determined in order male, female, male, etc., and eventually $$$⌈K/2⌉$$$ cages are needed.
However, since cages are not lost once they are bought, the maximum value of the answer is updated as follows.
ans = max(ans, k + ceil(t/2))
C,
Actually, We can always make all part as zero with this.
Div-1 E doesn't really require HLD, we can simply use two fenwick trees to find hash of a path in $$$O(\log{n})$$$ similarly to how we query path sum in trees.
Each path $$$u \rightarrow v$$$ can be divided into two subpaths $$$u \rightarrow lca(u, v)$$$ and $$$lca(u, v) \rightarrow v$$$.
We can now build two fenwick trees upon euler tour of the tree, one to find the hash of upward vertical path $$$u \rightarrow lca(u, v)$$$ and one to find the hash of downward vertical path $$$lca(u, v) \rightarrow v$$$.
($$$par_u$$$ is the parent of $$$u$$$ in dsu, $$$H$$$ is height of the tree, and $$$P$$$ is the number we chose for exponents in hash)
To find hash of upward vertical path $$$a \rightarrow b$$$, simply query range $$$[ExitTime_a, ExitTime_b]$$$ in the first fenwick tree. To find hash of downward vertical path $$$a \rightarrow b$$$, simply query range $$$[EntryTime_a, EntryTime_b]$$$ in the second fenwick. (Exponents can ofc be easily adjusted in $$$O(1)$$$ with $$$O(n\log{MOD})$$$ precomputation.)
Implementation: link
Can someone please explain Problem C- Music Festival solution? I did not understand the tutorial at all. vaaven
how does one get the logic of div2a (beautiful blanket) during contest?
Div 1D. Can we use the topological sort instead of dijkstra?
270590828 — My wa6 solution.
Div 1D, Can someone please help me with my submission? I have tried debugging for a few hours but I can't get it.
I suspect it is something to do with priority_queue ordering, WA6. Thanks.
Edit: solved