ОНОВЛЕННЯ! Масивне поліпшення продуктивності! n = 7 зараз закінчується за 10 хвилин! Дивіться пояснення внизу!
Це було гарно весело писати. Це вирішення цієї проблеми, написане у Funciton. Деякі фактори:
- Він приймає ціле число на STDIN. Будь-яке стороннє пробіл порушує його, включаючи новий рядок після цілого числа.
- Він використовує числа 0 до n - 1 (не 1 до n ).
- Він заповнює сітку "назад", тож ви отримуєте рішення там, де нижній рядок читається,
3 2 1 0
а не де читається верхній рядок 0 1 2 3
.
- Він правильно виводить
0
(єдине рішення) для n = 1.
- Порожній вихід для n = 2 і n = 3.
- Коли компілюється в exe, потрібно приблизно 8¼ хвилин протягом n = 7 (це було приблизно за годину до підвищення продуктивності). Без компіляції (за допомогою інтерпретатора) це займає приблизно в 1,5 рази довше, тому використання компілятора варто.
- Як особиста віха, я вперше написав цілу програму Funciton, не попередньо написавши програму мовою псевдокоду. Я написав це у фактичному С # першому, хоча.
- (Однак, це не перший раз, коли я вніс зміни, щоб масово покращити продуктивність чогось у Funciton. Перший раз, коли я це робив, був на факторній функції. Заміна порядку операндів множення зробила величезну зміну через як працює алгоритм множення . На всякий випадок, якщо вам було цікаво.)
Без зайвого галасу:
┌────────────────────────────────────┐ ┌─────────────────┐
│ ┌─┴─╖ ╓───╖ ┌─┴─╖ ┌──────┐ │
│ ┌─────────────┤ · ╟─╢ Ӂ ╟─┤ · ╟───┤ │ │
│ │ ╘═╤═╝ ╙─┬─╜ ╘═╤═╝ ┌─┴─╖ │ │
│ │ └─────┴─────┘ │ ♯ ║ │ │
│ ┌─┴─╖ ╘═╤═╝ │ │
│ ┌────────────┤ · ╟───────────────────────────────┴───┐ │ │
┌─┴─╖ ┌─┴─╖ ┌────╖ ╘═╤═╝ ┌──────────┐ ┌────────┐ ┌─┴─╖│ │
│ ♭ ║ │ × ╟───┤ >> ╟───┴───┘ ┌─┴─╖ │ ┌────╖ └─┤ · ╟┴┐ │
╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌─────┤ · ╟───────┴─┤ << ╟─┐ ╘═╤═╝ │ │
┌───────┴─────┘ ┌────╖ │ │ ╘═╤═╝ ╘══╤═╝ │ │ │ │
│ ┌─────────┤ >> ╟─┘ │ └───────┐ │ │ │ │ │
│ │ ╘══╤═╝ ┌─┴─╖ ╔═══╗ ┌─┴─╖ ┌┐ │ │ ┌─┴─╖ │ │
│ │ ┌┴┐ ┌───────┤ ♫ ║ ┌─╢ 0 ║ ┌─┤ · ╟─┤├─┤ ├─┤ Ӝ ║ │ │
│ │ ╔═══╗ └┬┘ │ ╘═╤═╝ │ ╚═╤═╝ │ ╘═╤═╝ └┘ │ │ ╘═╤═╝ │ │
│ │ ║ 1 ╟───┬┘ ┌─┴─╖ └───┘ ┌─┴─╖ │ │ │ │ │ ┌─┴─╖ │
│ │ ╚═══╝ ┌─┴─╖ │ ɓ ╟─────────────┤ ? ╟─┘ │ ┌─┴─╖ │ ├─┤ · ╟─┴─┐
│ ├─────────┤ · ╟─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴────┤ + ╟─┘ │ ╘═╤═╝ │
┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═╧═╕ ╔═══╗ ┌───╖ ┌─┴─╖ ┌─┴─╖ ╘═══╝ │ │ │
┌─┤ · ╟─┤ · ╟───┐ └┐ └─╢ ├─╢ 0 ╟─┤ ⌑ ╟─┤ ? ╟─┤ · ╟──────────────┘ │ │
│ ╘═╤═╝ ╘═╤═╝ └───┐ ┌┴┐ ╚═╤═╛ ╚═╤═╝ ╘═══╝ ╘═╤═╝ ╘═╤═╝ │ │
│ │ ┌─┴─╖ ┌───╖ │ └┬┘ ┌─┴─╖ ┌─┘ │ │ │ │
│ ┌─┴───┤ · ╟─┤ Җ ╟─┘ └────┤ ? ╟─┴─┐ ┌─────────────┘ │ │
│ │ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │ │╔════╗╔════╗ │ │
│ │ │ ┌──┴─╖ ┌┐ ┌┐ ┌─┴─╖ ┌─┴─╖ │║ 10 ║║ 32 ║ ┌─────────────────┘ │
│ │ │ │ << ╟─┤├─┬─┤├─┤ · ╟─┤ · ╟─┘╚══╤═╝╚╤═══╝ ┌──┴──┐ │
│ │ │ ╘══╤═╝ └┘ │ └┘ ╘═╤═╝ ╘═╤═╝ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ │
│ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ╔═╧═╕ └─┤ ? ╟─┤ · ╟─┤ % ║ │
│ └─────┤ · ╟─┤ · ╟──┤ Ӂ ╟──┤ ɱ ╟─╢ ├───┐ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │
│ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ ╘═══╝ ╚═╤═╛ ┌─┴─╖ ┌─┴─╖ │ └────────────────────┘
│ └─────┤ │ └───┤ ‼ ╟─┤ ‼ ║ │ ┌──────┐
│ │ │ ╘═══╝ ╘═╤═╝ │ │ ┌────┴────╖
│ │ │ ┌─┴─╖ │ │ │ str→int ║
│ │ └──────────────────────┤ · ╟───┴─┐ │ ╘════╤════╝
│ │ ┌─────────╖ ╘═╤═╝ │ ╔═╧═╗ ┌──┴──┐
│ └──────────┤ int→str ╟──────────┘ │ ║ ║ │ ┌───┴───┐
│ ╘═════════╝ │ ╚═══╝ │ │ ┌───╖ │
└───────────────────────────────────────────────────────┘ │ └─┤ × ╟─┘
┌──────────────┐ ╔═══╗ │ ╘═╤═╝
╔════╗ │ ╓───╖ ┌───╖ │ ┌───╢ 0 ║ │ ┌─┴─╖ ╔═══╗
║ −1 ║ └─╢ Ӝ ╟─┤ × ╟──┴──────┐ │ ╚═╤═╝ └───┤ Ӂ ╟─╢ 0 ║
╚═╤══╝ ╙───╜ ╘═╤═╝ │ │ ┌─┴─╖ ╘═╤═╝ ╚═══╝
┌─┴──╖ ┌┐ ┌───╖ ┌┐ ┌─┴──╖ ╔════╗ │ │ ┌─┤ ╟───────┴───────┐
│ << ╟─┤├─┤ ÷ ╟─┤├─┤ << ║ ║ −1 ║ │ │ │ └─┬─╜ ┌─┐ ┌─────┐ │
╘═╤══╝ └┘ ╘═╤═╝ └┘ ╘═╤══╝ ╚═╤══╝ │ │ │ └───┴─┘ │ ┌─┴─╖ │
│ └─┘ └──────┘ │ │ └───────────┘ ┌─┤ ? ╟─┘
└──────────────────────────────┘ ╓───╖ └───────────────┘ ╘═╤═╝
┌───────────╢ Җ ╟────────────┐ │
┌────────────────────────┴───┐ ╙───╜ │
│ ┌─┴────────────────────┐ ┌─┴─╖
┌─┴─╖ ┌─┴─╖ ┌─┴─┤ · ╟──────────────────┐
│ ♯ ║ ┌────────────────────┤ · ╟───────┐ │ ╘═╤═╝ │
╘═╤═╝ │ ╘═╤═╝ │ │ │ ┌───╖ │
┌─────┴───┘ ┌─────────────────┴─┐ ┌───┴───┐ ┌─┴─╖ ┌─┴─╖ ┌─┤ × ╟─┴─┐
│ │ ┌─┴─╖ │ ┌───┴────┤ · ╟─┤ · ╟──────────┤ ╘═╤═╝ │
│ │ ┌───╖ ┌───╖ ┌──┤ · ╟─┘ ┌─┴─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴─╖ │ │
│ ┌────┴─┤ ♭ ╟─┤ × ╟──┘ ╘═╤═╝ │ ┌─┴─╖ ┌───╖└┐ ┌──┴─╖ ┌─┤ · ╟─┘ │
│ │ ╘═══╝ ╘═╤═╝ ┌───╖ │ │ │ × ╟─┤ Ӝ ╟─┴─┤ ÷% ╟─┐ │ ╘═╤═╝ ┌───╖ │
│ ┌─────┴───┐ ┌────┴───┤ Ӝ ╟─┴─┐ │ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ │ │ └───┤ Ӝ ╟─┘
│ ┌─┴─╖ ┌───╖ │ │ ┌────╖ ╘═╤═╝ │ └───┘ ┌─┴─╖ │ │ └────┐ ╘═╤═╝
│ │ × ╟─┤ Ӝ ╟─┘ └─┤ << ╟───┘ ┌─┴─╖ ┌───────┤ · ╟───┐ │ ┌─┴─╖ ┌───╖ │ │
│ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌───┤ + ║ │ ╘═╤═╝ ├──┴─┤ · ╟─┤ × ╟─┘ │
└───┤ └────┐ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ ╘═╤═╝ │
┌─┴─╖ ┌────╖ │ ║ 0 ╟─┤ ? ╟─┤ = ║ ┌┴┐ │ ║ 0 ╟─┤ ? ╟─┤ = ║ │ │ ┌────╖ │
│ × ╟─┤ << ╟─┘ ╚═══╝ ╘═╤═╝ ╘═╤═╝ └┬┘ │ ╚═══╝ ╘═╤═╝ ╘═╤═╝ │ └─┤ << ╟─┘
╘═╤═╝ ╘═╤══╝ ┌┐ ┌┐ │ │ └───┘ ┌─┴─╖ ├──────┘ ╘═╤══╝
│ └────┤├──┬──┤├─┘ ├─────────────────┤ · ╟───┘ │
│ └┘┌─┴─╖└┘ │ ┌┐ ┌┐ ╘═╤═╝ ┌┐ ┌┐ │
└────────────┤ · ╟─────────┘ ┌─┤├─┬─┤├─┐ └───┤├─┬─┤├────────────┘
╘═╤═╝ │ └┘ │ └┘ │ └┘ │ └┘
└───────────────┘ │ └────────────┘
Пояснення першої версії
Перша версія потребувала приблизно години, щоб вирішити n = 7. Далі пояснюється здебільшого, як працювала ця повільна версія. Внизу я поясню, яку зміну я вніс, щоб досягти цього за 10 хвилин.
Екскурсія в шматочки
Для цієї програми потрібні біти. Для цього потрібно багато бітів, і вони потрібні у всіх потрібних місцях. Досвідчені програмісти Funciton вже знають, що якщо вам потрібно n біт, ви можете використовувати формулу
який у фунцитоні можна виразити як
Роблячи оптимізацію продуктивності, мені спало на думку, що я можу обчислити те саме значення набагато швидше, використовуючи цю формулу:
Сподіваюся, ви пробачте мені, що я не оновив усі графіки рівнянь у цій публікації відповідно.
Тепер, скажімо, ви не хочете суцільного блоку бітів; насправді, ви хочете n біт через рівні проміжки часу кожен k- ний біт, як-от так:
LSB
↓
00000010000001000000100000010000001
└──┬──┘
k
Формула для цього досить зрозуміла, як тільки ви це знаєте:
У коді функція Ӝ
приймає значення n і k і обчислює цю формулу.
Слідкуйте за використаними номерами
У підсумковій сітці є n ² числа, і кожне число може бути будь-яким із n можливих значень. Для того, щоб відслідковувати, які числа дозволені в кожній комірці, ми підтримуємо число, що складається з n ³ біт, в якому встановлюється біт, який вказує на те, що прийнято певне значення. Спочатку це число 0, очевидно.
Алгоритм починається в правому нижньому куті. Після «відгадування» першим числом є 0, нам слід відслідковувати той факт, що 0 більше не дозволено в жодній комірці вздовж того ж рядка, стовпця та діагоналі:
LSB (example n=5)
↓
10000 00000 00000 00000 10000
00000 10000 00000 00000 10000
00000 00000 10000 00000 10000
00000 00000 00000 10000 10000
10000 10000 10000 10000 10000
↑
MSB
Для цього обчислюємо наступні чотири значення:
Поточний рядок: нам потрібно n біт кожного n -го біта (по одному на комірку), а потім перенести його на поточний рядок r , пам'ятаючи, що кожен рядок містить n ² біт:
Поточний стовпчик: нам потрібно n біт кожен n ²-й біт (по одному на рядок), а потім перенести його на поточний стовпець c , пам'ятаючи, що кожен стовпець містить n біт:
Діагональ вперед: нам потрібні n біт кожні ... (ви звернули увагу? Швидке, зрозумійте!) ... n ( n +1) -міт (добре зроблено!), Але тільки якщо ми насправді на передня діагональ:
Діагональ назад: Тут дві речі. По-перше, як ми можемо знати, чи перебуваємо ми на відсталій діагоналі? Математично умовою є c = ( n - 1) - r , що таке ж, як c = n + (- r - 1). Гей, це щось вам нагадує? Це правильно, це доповнення двох, тому ми можемо використовувати побітове заперечення (дуже ефективне у Funciton) замість декременту. По-друге, наведена вище формула передбачає, що ми хочемо встановити найменш суттєвий біт, але в діагоналі відсталого ми цього не зробимо, тому нам доведеться змістити його на ... ви знаєте? ... Правильно, n ( n - 1).
Це також єдиний, де ми потенційно ділимо на 0, якщо n = 1. Однак Funciton не хвилює. 0 ÷ 0 - це лише 0, ви не знаєте?
У коді функція Җ
(нижня) бере n та індекс (з якого вона обчислює r і c шляхом поділу та решти), обчислює ці чотири значення та or
s їх разом.
Алгоритм грубої сили
Алгоритм грубої сили реалізований Ӂ
(функція вгорі). Він бере n (розмір сітки), індекс (де в сітці ми зараз розміщуємо число), і береться (число з n ³ бітами, яке вказує нам, які числа ми можемо розмістити у кожній комірці).
Ця функція повертає послідовність рядків. Кожна рядок є повноцінним рішенням сітки. Це повний вирішувач; він би повертав усі рішення, якщо ви дозволите, але він повертає їх як послідовно оцінену послідовність.
Якщо індекс досяг 0, ми успішно заповнили всю сітку, тому повертаємо послідовність, що містить порожню рядок (єдине рішення, яке не охоплює жодної з комірок). Порожній рядок є 0
, і ми використовуємо функцію бібліотеки, ⌑
щоб перетворити його в одноелементну послідовність.
Перевірка, описана в поліпшенні продуктивності нижче, відбувається тут.
Якщо індекс ще не дорівнював 0, ми зменшуємо його на 1, щоб отримати індекс, на якому зараз нам потрібно поставити число (зателефонуйте цьому ix ).
Ми використовуємо ♫
для створення ледачої послідовності, що містить значення від 0 до n - 1.
Тоді ми використовуємо ɓ
(монадичне зв’язування) з лямбда, яка робить наступне для того, щоб:
- Спочатку подивіться на відповідний біт, який взяли, щоб вирішити, чи дійсне тут номер чи ні. Ми можемо розмістити число i, якщо і лише тоді, коли прийнято & (1 << ( n × ix ) << i ) ще не встановлено. Якщо вона встановлена, поверніться
0
(порожня послідовність).
- Використовуйте
Җ
для обчислення бітів, що відповідають поточному рядку, стовпцю та діагоналі. Зсуньте його на i, а потім or
на зняте .
- Рекурсивно вимагають
Ӂ
отримати всі рішення для осередків, що залишилися, передаючи їм нові прийняті та зменшені ix . Це повертає послідовність неповних рядків; кожен рядок містить ix символи (сітка заповнена до індексу ix ).
- Використовуйте
ɱ
(карту) для перегляду знайдених таким чином рішень і використовуйте ‼
для об'єднання i до кінця кожного. Додайте новий рядок, якщо індекс кратний n , інакше пробіл.
Генерування результату
Основна програма викликає Ӂ
(грубий форсер) з n , індекс = n ² (пам'ятаємо, ми заповнюємо сітку назад) і приймаємо = 0 (спочатку нічого не береться). Якщо результат цього - порожня послідовність (рішення не знайдено), виведіть порожню рядок. В іншому випадку виведіть перший рядок у послідовності. Зауважте, що це означає, що він оцінить лише перший елемент послідовності, тому розв’язувач не продовжується, поки не знайде всі рішення.
Поліпшення продуктивності
(Для тих, хто вже читав стару версію пояснення: програма більше не генерує послідовність послідовностей, яку потрібно окремо перетворити на рядок для виведення; вона просто генерує послідовність рядків безпосередньо. Я відповідно відредагував пояснення . Але це було не головне поліпшення. Ось воно і приходить.)
На моїй машині складений exe першої версії знадобився майже рівно 1 годину, щоб вирішити n = 7. Це не було протягом заданого часового обмеження 10 хвилин, тому я не відпочивав. (Ну, насправді, я не відпочивав через те, що в мене з'явилася ідея про те, як їх масово пришвидшити.)
Описаний вище алгоритм зупиняє його пошук і відслідковує кожен раз, коли він стикається з коміркою, в якій встановлені всі біти у прийнятому номері, що вказує на те, що в цю клітинку нічого не можна вставити.
Однак алгоритм продовжуватиме марно заповнювати сітку до комірки, в якій встановлені всі ці біти. Це було б набагато швидше, якби воно могло зупинитися, як тільки в будь - якій ще заповненій комірці вже встановлені всі біти, що вже вказує на те, що ми не можемо вирішити решту сітки незалежно від того, які цифри ми поставимо це. Але як ви ефективно перевіряєте, чи встановлена будь-яка клітинка n своїх бітів, не проходячи їх усіх?
Трюк починається з додавання одного біта на комірку до прийнятого числа. Замість того, що було показано вище, тепер це виглядає так:
LSB (example n=5)
↓
10000 0 00000 0 00000 0 00000 0 10000 0
00000 0 10000 0 00000 0 00000 0 10000 0
00000 0 00000 0 10000 0 00000 0 10000 0
00000 0 00000 0 00000 0 10000 0 10000 0
10000 0 10000 0 10000 0 10000 0 10000 0
↑
MSB
Замість n ³ в цьому числі є n ² ( n + 1) біт. Функція, яка заповнює поточний рядок / стовпчик / діагональ, була відповідно змінена (насправді, повністю переписана, щоб бути чесною). Ця функція все ще заповнює лише n біт на комірку, тому додатковий біт, який ми тільки що додали, завжди буде 0
.
Тепер, скажімо, ми перебуваємо на півдорозі обчислення, ми просто помістили 1
в середню комірку, і взяте число виглядає приблизно так:
current
LSB column (example n=5)
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0
00011 0 11110 0 01101 0 11101 0 11100 0
11111 0 11110 0[11101 0]11100 0 11100 0 ← current row
11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
↑
MSB
Як бачимо, верхньо-ліва клітина (індекс 0) та клітина середньої лівої (індекс 10) тепер неможливі. Як ми найбільш ефективно це визначаємо?
Розглянемо число, в якому встановлено 0-й біт кожної комірки, але тільки до поточного індексу. Таке число легко обчислити за знайомою формулою:
Що ми отримали, якби додати ці два числа разом?
LSB LSB
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0 10000 0 10000 0 10000 0 10000 0 10000 0 ╓───╖
00011 0 11110 0 01101 0 11101 0 11100 0 ║ 10000 0 10000 0 10000 0 10000 0 10000 0 ║
11111 0 11110 0 11101 0 11100 0 11100 0 ═══╬═══ 10000 0 10000 0 00000 0 00000 0 00000 0 ═════ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ║ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 00000 0 00000 0 00000 0 00000 0 00000 0 o
↑ ↑
MSB MSB
Результат:
OMG
↓
00000[1]01010 0 11101 0 00010 0 00011 0
10011 0 00001 0 11101 0 00011 0 00010 0
═════ 00000[1]00001 0 00011 0 11100 0 11100 0
═════ 11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
Як бачимо, додавання переливається в додатковий біт, який ми додали, але тільки якщо всі біти для цієї комірки встановлені! Тому все, що вам залишається зробити, це замаскувати ці біти (така ж формула, як вище, але << n ) і перевірити, чи результат 0:
00000[1]01010 0 11101 0 00010 0 00011 0 ╓╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓─╖ ╓───╖
10011 0 00001 0 11101 0 00011 0 00010 0 ╓╜╙╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓╜ ╙╖ ║
00000[1]00001 0 00011 0 11100 0 11100 0 ╙╥╥╜ 00000 1 00000 1 00000 0 00000 0 00000 0 ═════ ║ ║ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ╓╜╙╥╜ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╙╖ ╓╜ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 ╙──╨─ 00000 0 00000 0 00000 0 00000 0 00000 0 ╙─╜ o
Якщо вона не дорівнює нулю, сітка неможлива, і ми можемо зупинитися.