На форумі обговорюються лише питання, пов'язані з олімпіадою
Ви не зайшли.
Повідомлення про завершення туру ще не було, але рішення вже перевірені, так що думаю, розв'язками можна ділитися.
Задачі цього року трохи простіші, ніж зазвичай (і суб'єктивно і судячи з таблиці результатів, де у 44 учасників 250+ балів. Зазвичай таких не більше 15-20).
Не всі розбори дуже докладні, але думаю, ідеї в більшості мають бути зрозумілими.
Удачі учасникам 4 туру ![]()
Train2020
Помітимо, що якщо вагони N та M зчеплені за рахунок спільного дільника K, то між N та M можна вставити довільну кількість чисел, що діляться на K. Тобто якщо вагон можна з'єднати хоча-б з двома іншими вагонами - його точно можна буде кудись прилаштувати (мабуть можна і математично/строго це довести, але я не доводив). Усі такі вагони можна взяти в поїзд.
Якщо у вагону є лише один потенційний сусід - такий вагон повинен буде стояти в голові, або в хвості поїзда. Тобто таких вагонів можна взяти не більше двох.
Ну і якщо для вагону немає сусідів - включити його в поїзд не вийде.
Підрахуємо ці кількості.
У вагону з номером K немає жодного сусіда тоді і тільки тоді, коли K - просте число і N < 2 * K.
У вагону з номером K є лише один сусід тоді і тільки тоді, коли K - просте число і 2 * K ≤ N < 3 * K.
В усіх інших випадках у вагонів є щонайменше 2 сусіди.
Таким чином, задача розв'язується за один прохід решета Ератосфена:
З самого початку ініціалізуємо відповідь значенням N, а кількість вагонів з одинм сусідом - нулем.
Запускаємо решето Ератосфена.
Коли знайшли чергове просте число p:
якщо N < 2 * p - зменшуємо відповідь на 1 (знайшли вагон без сусідів)
якщо 2 * p ≤ N < 3 * p - збільшуємо на 1 кількість вагонів з одинм сусідом.
Якщо після закінчення роботи решета кількість вагонів з одним сусідом більша за 2 - зменшуємо відповідь на ("кількість вагонів з одинм сусідом" - 2).
Асимптотика.
Звичайна асимптотика решета Ератосфена.
Час: O(N * log log N)
Пам'ять: O(N)
Код рішення на С++.
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int solve(int n) {
constexpr int MAX_N = 100000;
if (n == 2 || n == 3) return 2;
int res = n;
int n_single_neighbour = 0;
bool prime[MAX_N + 1];
fill_n(prime, n + 1, true);
for (int p = 2; p <= n; ++p) {
if (prime[p]) {
for (int i = p * 2; i <= n; i += p) prime[i] = false;
res -= p * 2 > n;
n_single_neighbour += p * 2 <= n && p * 3 > n;
}
}
return res - max(0, n_single_neighbour - 2);
}
int main() {
int n;
cin >> n;
cout << solve(n) << endl;
}Game2020
Гра зводиться до класичної гри Нім згідно теореми Шпрага-Гранді. Розмірами купок тут будуть суми розмірів непустих кошиків, що йдуть підряд. Наприклад, тесту
10 1 0 1 2 3 0 7 5 0 9
відповідає гра в Нім з розмірами купок
1 6 12 9
Доведемо цю відповідність (якщо цікавить лише рішення, а не доведення його коректності, то розділ можна пропустити)
Якщо група кошиків складається з одного кошика - відповідність очевидна,
адже над кошиком можна виконувати ті ж самі операції, що й над Нім-купкою
і жодних інших. Якщо ж група складається з кількох кошиків - крім
звичайних операцій над купкою (зменшення) гравцю стає доступною ще й
операція "зменшення з розділенням". Наприклад:
1 2 3 -> 1 0 3
або
1 2 3 4 5 -> 1 0 2 0 3
Нехай початкова сума групи кошиків була N, гравець взяв всього X цукерок
і отримав K нових груп розмірами M[i]. g(N) - значення Шпрага-Гранді для
групи розміром N.
У такому випадку згідно з теоремою, значенням Шпрага-Гранді для N буде
g(N) = mex (states), де states =
{0, ..., N - 1} адже гравець все ще може взяти від 1 до N цукерок, не розділяючи групу
∪
{g(M[1] ⊕ ... ⊕ M[K]) для всих можливих X та всих можливих розділень}
Для доведення відповідності нам необхідно і достатньо показати, що серед
значень g(M[1] ⊕ ... ⊕ M[K]) немає значення N.
Показати це нескладно, адже XOR-сума не може бути більшою за алгебраїчну
суму. Тобто усі значення M[1] ⊕ ... ⊕ M[K] будуть менші або рівні за (N - X).
А оскільки X > 0, то M[1] ⊕ ... ⊕ M[K] < N.Таким чином, рішення доволі просте - пробігаємося по вхідному масиву, рахуємо суми розмірів груп непустих кошиків і xor-имо ці суми.
Зауваження по реалізації. Важливо правильно обрати формат даних, адже хоча окремі числа і менші за 10^5, але їх може бути 10^6 штук, і ми їх сумуємо. Тобто загальне значення може бути до 10^11 і в 32-бітний інт воно не влізе.
Асимптотика.
Час: O(N)
Пам'ять: O(1)
Код рішення на С++.
#include <iostream>
using namespace std;
long long sprague_grundy(int n, int *a) {
long long res = 0;
long long sum = 0;
for (int i = 0; i < n; ++i) {
sum += a[i];
if (a[i] == 0) {
res ^= sum;
sum = 0;
}
}
res ^= sum;
return res;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t;
cin >> t;
string s;
while (t--) {
static int a[1000000];
int n;
cin >> n;
for (int i = 0; i < n; ++i) cin >> a[i];
s += sprague_grundy(n, a) ? 'N' : 'Y';
}
cout << s << endl;
}Wall2020
На каналі 3Blue1Brown є серія відео, присвячених цій задачі. Підозрюю, що автор задачі надихався саме ними ![]()
Наполегливо рекомендую переглянути ці та інші відео з каналу. Там багато цікавого для тих, хто любить математику.
Власне серія відео:
https://www.youtube.com/watch?v=HEfHFsfGXjs
https://www.youtube.com/watch?v=jsYwFizhncE
https://www.youtube.com/watch?v=brU5yLm9DZM
Більш строгий документ з описом експерименту, купою математики і строгим виведенням того, як обчислити число пі за допомогою двох ідеально пружних кубів на ідеально рівній поверхні:
https://www.maths.tcd.ie/~lebed/Galperi … h%20pi.pdf
Можливо пізніше додам основні моменти з відео в пост, але мені поки що лінь, а відео хороше ![]()
Асимптотика.
Час: O(1)
Пам'ять: O(1)
Код рішення на С++.
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const long double pi = 3.141592653589793238462643383279502884L;
int main() {
long double m1, m2;
cin >> m1 >> m2;
auto theta = atan(sqrt(m1 / m2));
int result = static_cast<int>(pi / theta);
if (pi - result * theta < 1e-6L) {
--result;
}
cout << result << endl;
}Trunks
Відповіддю є послідовність A059881 з OEIS, яка в свою чергу є сумою послідовностей A059882 та A059883.
Задача розв'язується за допомогою ДП.
Нехай
T[n][k] - кількість способів набрати n монет, взявши з першої скрині рівно k монет, з другої - більше за k, з третьої - менше, ніж з другої і т.д.
S[n][k] - кількість способів набрати n монет, взявши з першої скрині рівно k монет, з другої - менше за k, з третьої - більше, ніж з другої і т.д.
тоді
T[n][k] дорівнюватиме сумі S[n-k][j] для усих j від k+1 до n-k включно
S[n][k] дорівнюватиме сумі T[n-k][j] для усих j від 1 до k-1 включно
І база ДП: T[n][n] = S[n][n] = 1
Якщо реалізувати таку динаміку влоб, то отримаємо кубічну асимптотику і в ТЛ не вліземо. Але можна дуже легко перетворити підрахунок суми з лінійного на константний, якщо зберігати в наших таблицях не самі значення T[n][k] та S[n][k], а префіксні суми. Тобто суми значень від T[n][0] до T[n][k] та від S[n][0] до S[n][k] відповідно.
І оскільки із застосуванням префіксних сум нам доведеться іноді віднімати значення - треба не забути в кінці зробити значення відповіді додатнім ((res % MOD + MOD) % MOD).
Асимптотика.
Час: O(N^2)
Пам'ять: O(N^2)
Код рішення на С++.
#include <iostream>
using namespace std;
int solve(int n, int k) {
constexpr size_t MAX_N = 5001;
constexpr int MOD = 1000000007;
if (n == k) return 1;
static int dp[MAX_N][MAX_N][2] = {};
for (int total = 1; total <= n; ++total) {
for (int first = 1; first < total; ++first) {
int inc_addend = (total - first > first) * (dp[total - first][total - first][0] - dp[total - first][first][0]) % MOD;
dp[total][first][0] = (dp[total][first - 1][0] + dp[total - first][first - 1][1]) % MOD;
dp[total][first][1] = (dp[total][first - 1][1] + inc_addend) % MOD;
}
dp[total][total][0] = (dp[total][total - 1][0] + 1) % MOD;
dp[total][total][1] = (dp[total][total - 1][1] + 1) % MOD;
for (int i = total + 1; i <= n; ++i) {
dp[total][i][0] = dp[total][total][0];
dp[total][i][1] = dp[total][total][1];
}
}
int resDec = (dp[n][k][0] - dp[n][k-1][0]) % MOD;
int resInc = (dp[n][k][1] - dp[n][k-1][1]) % MOD;
int res = (resInc + resDec) % MOD;
return (res + MOD) % MOD;
}
int main() {
int n, k;
cin >> n >> k;
cout << solve(n, k) << endl;
}Kingdoms2020
Ключ до розв'язку - послідовність A051034 з OEIS, або більш загально - гіпотеза Ґольдбаха.
Відмітимо кілька речей:
1. Будь-яке число можна представити у вигляді суми трьох або менше простих чисел.
2. У вигляді суми двох простих чисел можна представити усі парні числа, більші за 2, а також непарні числа N такі, що N-2 - просте.
Таким чином, для розв'язку розділимо вхідний діапазон N на 3 частини:
1. N ≥ 3
2. N = 1
3. N = 2
Для першої частини відповіддю буде (c - d + 1) * (b - max(a, 3) + 1), адже як ми з'ясували, для N ≥ 3 існують усі (N, K)-царства.
Для другої частини відповіддю буде кількість простих чисел у діапазоні [c, d].
Для третьої - кількість чисел i у діапазоні [c, d] таких, що
1. число i просте АБО
2. число i парне АБО
3. число i - 2 просте
Враховуючи дуже невеликі обмеження, особливих вимог до реалізації немає і вона може бути якою завгодно. Все одно працюватиме швидко. Хіба що на простоту варто перевіряти за корінь, а не лінійно. Ну і пам'ятати про граничні випадки (для K = 1 не існує жодного (N, K)-царства).
Асимптотика.
Час: O((d - c) * sqrt(d))
Пам'ять: O(1)
Код рішення на С++.
#include <iostream>
#include <algorithm>
using namespace std;
bool is_prime(int n) {
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (int i = 5; i * i <= n; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
int solve(int minN, int maxN, int minK, int maxK) {
minK = max(minK, 2);
if (maxK < minK) return 0;
int result = (maxK - minK + 1) * max(0, maxN + 1 - max(minN, 3));
if (minN >= 3) return result;
maxN = min(maxN, 2);
for (int i = minK; i <= maxK; ++i) {
if (is_prime(i)) {
result += maxN - minN + 1;
} else if (maxN >= 2 && (i % 2 == 0 || is_prime(i - 2))) {
result++;
}
}
return result;
}
int main() {
int a, b, c, d;
cin >> a >> b >> c >> d;
cout << solve(a, b, c, d) << endl;
}Поза форумом
Засів за читанням, дякую за розбори, допомагає зрозуміти краще задачі.
Поза форумом