Полічити масиви періодів


11

periodРядків є найкоротшим ненульовим зрушенням так , що рядок відповідає самому собі, ігноруючи будь-які деталі , які сверхмандати. Так, наприклад, abcabcabмає період 3. За умовою ми говоримо, що якщо такого зрушення немає, то рядок має період, рівний його довжині. Отже, період abcdeє 5і період aє 1.

Більш формально, період рядка Sє мінімальним, i > 0так що S[1,n-i] == S[i+1,n](індексація від 1).

Для заданого рядка S потужністю дві довжини обчислимо період усіх його префіксів потужністю дві довжини. Наприклад, розглянемо S = abcabcab. Періоди, які ми будемо обчислювати:

'a', 1
'ab', 2
'abca', 3
'abcabcab', 3

Ми фактично просто виведемо масив періодів, тобто [1, 2, 3, 3].

Для заданої позитивної сили двох nрозглянути всі можливі бінарні рядки S. Нагадаємо, що двійковий рядок - це просто рядок 1s і 0s, тому існують саме 2^nтакі рядки (тобто 2до потужності n). Для кожного з них ми можемо обчислити цей масив періодів.

Завдання полягає в тому, щоб написати код, який приймає n(потужність двох) в якості вхідного сигналу, і обчисли, скільки існує різних таких масивів.

Відповіді n = 1, 2, 4, 8, 16, 32, 64, 128:

1, 2, 6, 32, 320, 6025, 216854, 15128807

Повний набір різних масивів періодів для n = 4:

1, 1, 1
1, 1, 3
1, 1, 4
1, 2, 2
1, 2, 3
1, 2, 4

Оцінка

Я запускатиму ваш код на своєму комп’ютері під управлінням Ubuntu протягом 10 хвилин. Ваш показник найбільший, nза який ваш код припиняється за цей час. Що стосується нічовини, nвиграє відповідь, яка завершує спільний найбільший швидкий. У випадку, якщо у таймінгу є нічия протягом 1 секунди, перша розміщена відповідь виграє.

Мови та бібліотеки

Ви можете використовувати будь-яку доступну мову та бібліотеки, які вам подобаються. Будь ласка, додайте повне пояснення, як запустити / скласти свій код в Linux, якщо це можливо. "

Ваш код повинен насправді обчислювати відповіді, а не, наприклад, лише виводити попередньо обчислені значення.

Провідні записи

  • 2 хвилини та 21 секунду при n = 128 в C # Пітера Тейлора
  • 9 секунд при n = 32 в Rust по isaacg

Це мені боліло голову.
Генрі

1
Завдання є цікавим, але я все ще не можу побачити об'єктивний критерій, який ви використовуєте, щоб розрізнити "попередньо обчислені" та "фактично обчислені" відповіді. Якщо ви, наприклад, не можете зрозуміти, як працює мій код, але він дає правильні відповіді на величезний n, чи прийняли б ви його? Невірно визначено, де знаходиться межа між жорстким кодуванням та фактичними обчисленнями.


1
@ThePirateBay codegolf.meta.stackexchange.com/a/1063/9206 . Це стандартне правило.

2
@Cowsquack Усі, крім перших трьох літер рядка, є abcab. Усі, крім останніх 3 листів, є abcab. Ці збіги та видалення меншої кількості літер не відповідають.
isaacg

Відповіді:


9

C #, n = 128 приблизно в 2:40

using System;
using System.Collections.Generic;
using System.Linq;

namespace Sandbox
{
    class PPCG137436
    {
        public static void Main(string[] args)
        {
            if (args.Length == 0) args = new string[] { "1", "2", "4", "8", "16", "32", "64", "128" };

            foreach (string arg in args)
            {
                Console.WriteLine(Count(new int[(int)(0.5 + Math.Log(int.Parse(arg)) / Math.Log(2))], 0));
            }
        }

        static int Count(int[] periods, int idx)
        {
            if (idx == periods.Length)
            {
                //Console.WriteLine(string.Join(", ", periods));
                return 1;
            }

            int count = 0;
            int p = idx == 0 ? 1 : periods[idx - 1];
            for (int q = p; q <= 1 << (idx + 1); q++)
            {
                periods[idx] = q;
                if (q == p || q > 1 << idx || p + q - Gcd(p, q) > 1 << idx && UnificationPasses(periods, idx, q)) count += Count(periods, idx + 1);
            }

            return count;
        }

        private static int Gcd(int a, int b)
        {
            while (a > 0) { int tmp = a; a = b % a; b = tmp; }
            return b;
        }

        private static bool UnificationPasses(int[] periods, int idx, int q)
        {
            UnionSet union = new UnionSet(1 << idx);
            for (int i = 0; i <= idx; i++)
            {
                for (int j = 0; j + periods[i] < Math.Min(2 << i, 1 << idx); j++) union.Unify(j, j + periods[i]);
            }

            IDictionary<int, long> rev = new Dictionary<int, long>();
            for (int k = 0; k < 1 << idx; k++) rev[union.Find(k)] = 0L;
            for (int k = 0; k < 1 << idx; k++) rev[union.Find(k)] |= 1L << k;

            long zeroes = rev[union.Find(0)]; // wlog the value at position 0 is 0

            ISet<int> onesIndex = new HashSet<int>();

            // This can be seen as the special case of the next loop where j == -1.
            for (int i = 0; i < idx; i++)
            {
                if (periods[i] == 2 << i) onesIndex.Add((2 << i) - 1);
            }
            for (int j = 0; j < idx - 1 && periods[j] == 2 << j; j++)
            {
                for (int i = j + 1; i < idx; i++)
                {
                    if (periods[i] == 2 << i)
                    {
                        for (int k = (1 << j) + 1; k <= 2 << j; k++) onesIndex.Add((2 << i) - k);
                    }
                }
            }

            for (int i = 1; i < idx; i++)
            {
                if (periods[i] == 1) continue;

                int d = (2 << i) - periods[i];
                long dmask = (1L << d) - 1;
                if (((zeroes >> 1) & (zeroes >> periods[i]) & dmask) == dmask) onesIndex.Add(periods[i] - 1);
            }

            long ones = 0L;
            foreach (var key in onesIndex) ones |= rev[union.Find(key)];

            if ((zeroes & ones) != 0) return false; // Definite contradiction!

            rev.Remove(union.Find(0));
            foreach (var key in onesIndex) rev.Remove(key);

            long[] masks = System.Linq.Enumerable.ToArray(rev.Values);

            int numFilteredMasks = 0;
            long set = 0;
            long M = 0;
            for (int i = 1; i <= idx; i++)
            {
                if (periods[i - 1] == 1) continue;

                // Sort the relevant masks to the start
                if (i == idx) numFilteredMasks = masks.Length; // Minor optimisation: skip the filter because we know we need all the masks
                long filter = (1L << (1 << i)) - 1;
                for (int j = numFilteredMasks; j < masks.Length; j++)
                {
                    if ((masks[j] & filter) != 0)
                    {
                        var tmp = masks[j];
                        masks[j] = masks[numFilteredMasks];
                        masks[numFilteredMasks++] = tmp;
                    }
                }

                // Search for a successful assignment, using the information from the previous search to skip a few initial values in this one.
                set |= (1L << numFilteredMasks) - 1 - M;
                M = (1L << numFilteredMasks) - 1;
                while (true)
                {
                    if (TestAssignment(periods, i, ones, masks, set)) break;
                    if (set == 0) return false; // No suitable assignment found

                    // Gosper's hack with variant to reduce the number of bits on overflow
                    long c = set & -set;
                    long r = set + c;
                    set = (((r ^ set) >> 2) / c) | (r & M);
                }
            }

            return true;
        }

        private static bool TestAssignment(int[] periods, int idx, long ones, long[] masks, long assignment)
        {
            for (int j = 0; j < masks.Length; j++, assignment >>= 1) ones |= masks[j] & -(assignment & 1);
            for (int i = idx - 1; i > 0; i--) // i == 0 is already handled in the unification process.
            {
                if (Period(ones, 2 << i, periods[i - 1]) < periods[i]) return false;
            }

            return true;
        }

        private static int Period(long arr, int n, int min)
        {
            for (int p = min; p <= n; p++)
            {
                // If the bottom n bits have period p then the bottom (n-p) bits equal the bottom (n-p) bits of the integer shifted right p
                long mask = (1L << (n - p)) - 1L;
                if ((arr & mask) == ((arr >> p) & mask)) return p;
            }

            throw new Exception("Unreachable");
        }

        class UnionSet
        {
            private int[] _Lookup;

            public UnionSet(int size)
            {
                _Lookup = new int[size];
                for (int k = 0; k < size; k++) _Lookup[k] = k;
            }

            public int Find(int key)
            {
                var l = _Lookup[key];
                if (l != key) _Lookup[key] = l = Find(l);
                return l;
            }

            public void Unify(int key1, int key2)
            {
                int root1 = Find(key1);
                int root2 = Find(key2);

                if (root1 < root2) _Lookup[root2] = root1;
                else _Lookup[root1] = root2;
            }
        }
    }
}

Розширення до n = 256 вимагає переключення на BigIntegerмаски, що, ймовірно, вбиває продуктивність занадто багато, щоб n = 128 пройшло без нових ідей, не кажучи вже про n = 256.

У Linux компілюйте mono-cscта виконайте mono.

Основне пояснення

Я не збираюся робити розсічення по рядках, а лише огляд понять.

Як правило, я радий повторити порядок 2 50 елементів у грубій силі комбінаторної програми. Тому для досягнення n = 128 необхідно використовувати підхід, який не аналізує кожен битстринг. Отже, замість того, щоб працювати вперед від бітових рядків до послідовностей періодів, я працюю назад: дається послідовність періодів, чи існує бітстринг, який реалізує це? Для n = 2 x існує проста верхня межа 2- х послідовностей ( x + 1) / 2 (проти 2 2- х біт-рядків).

У деяких аргументах використовується лемма рядкової періодичності :

Нехай pі qбуде два періоди довжини рядка n. Якщо p + q ≤ n + gcd(p, q)тоді gcd(p, q)також є період рядка.

Wlog Я припускаю, що всі розглянуті бітстриги починаються з 0.

Враховуючи послідовність періодів, де є період префікса довжиною 2 i ( завжди), є кілька простих спостережень щодо можливих значень :[p1 p2 ... pk]pip0 = 1pk+1

  • pk+1 ≥ pkоскільки період рядка Sтакож є періодом будь-якого префікса S.

  • pk+1 = pk завжди можливе розширення: просто повторіть ту саму примітивну рядок у два рази більше символів.

  • 2k < pk+1 ≤ 2k+1завжди можливе розширення. Це достатньо для того, щоб показати це, тому що аперіодичний рядок довжиною може бути розширений до апериодичної рядки довжиною , додаючи будь-яку літеру, яка не є її першою літерою.pk+1 = 2k+1LL+1

    Візьміть рядок Sxдовжиною 2 k , період якого є, і вважайте рядок довжиною 2 k + 1 . Ясно , що є в період 2 до +1. Припустимо, його період менший.pkSxySSxySq

    Тож лемма періодичності також є періодом , і оскільки найбільший дільник є меншим або рівним її аргументам і є найменшим періодом, ми повинні бути належним коефіцієнтом 2 k +1. Оскільки його коефіцієнт не може бути 2, ми маємо .2k+1 + q ≤ 2k+1+1 ≤ 2k+1 + gcd(2k+1, q)gcd(2k+1, q)SxySqqq ≤ (2k+1)/3

    Тепер, оскільки це період, він повинен бути періодом . Але період є . У нас є два випадки:q ≤ 2kSxySSxSxpk

    1. gcd(pk, q) = pk, або рівнозначно ділиться точно на .pkq
    2. pk + q > 2k + gcd(pk, q) так що лема періодичності не примушує менший період.

    Розглянемо спочатку другий випадок. , що суперечить визначенню як періоду Росії . Тому ми змушені робити висновок, що є фактором .pk > 2k + gcd(pk, q) - q ≥ 2k+1 - q ≥ 2k+1 - (2k+1)/3 ≥ 2qpkSxpkq

    Але оскільки qє періодом Sxі є періодом , префікс довжини - це лише копії префікса довжини , тому ми бачимо, що це також період .pkSxqq/pkpkpkSxyS

    Тому період SxySдорівнює або 2 к +1. Але у нас є два варіанти ! Щонайменше один вибір дасть період , тож хоча б один дасть період 2 k +1. QED.pkyypk

  • Лема періодичності дозволяє нам відкинути деякі можливі розширення.

  • Будь-яке розширення, яке не пройшло тест швидкого прийняття або швидкого відхилення, повинно бути перевірено конструктивно.

Побудова бітового рядка з заданою послідовністю періодів - це, по суті, проблема задоволення, але вона має багато структури. Існують прості обмеження рівності, що мають на увазі кожен період префікса, тому я використовую структуру даних набору об'єднань, щоб об'єднати біти в незалежні кластери. Цього було достатньо для вирішення n = 64, але для n = 128 потрібно було йти далі. Я використовую два корисні аргументи:2k - pk

  1. Якщо префікс довжини Mдорівнює, а префікс довжини має період, то префікс довжини повинен закінчуватися . Це найпотужніше саме у тих випадках, коли б інакше було більшість незалежних кластерів, що зручно.01M-1L > MLL1M
  2. Якщо префікс довжини Mє, а префікс довжини має період з і закінчується, то він повинен фактично закінчуватися . Це найпотужніше в протилежній крайності, коли послідовність періодів починається з безлічі одиниць.0ML > ML - dd < M0d10d

Якщо ми не отримаємо негайного протиріччя, змусивши кластер з першим бітом (який вважається рівним нулю) дорівнює одному, тоді ми грубою силою (з деякими мікрооптимізаціями) звертаємось до можливих значень для невимушених кластерів. Зауважте, що порядок у низхідній кількості одиниць, тому що, якщо ith біт є одиницею, то період не може бути, iі ми хочемо уникати періодів, коротших, ніж ті, які вже виконуються кластеризацією. Зниження збільшує шанси на те, щоб знайти дійсне завдання завчасно.


Це справді велике досягнення! Я дуже вражений.

@Lembik, я спростив і оптимізував код і скоротив час виконання n = 128 приблизно на третину.
Пітер Тейлор

1
Я хотів би знати, який алгоритм ви створили для цього. Ваш код має надзвичайно мало логіки в ньому, і він повинен робити щось дуже розумно.

7

Іржа, 32, 10с 11с 29 на моєму ноутбуці

Викликайте його з бітним розміром як аргументом командного рядка.

Розумні прийоми: представляйте бітстринг безпосередньо як числа, використовуйте bittwiddling для перевірки циклів. Шукайте лише першу половину біткордних рядків, починаючи з 0, оскільки масив періодів бітстрингу та його зворотний (0s поміняються на 1s) однакові. Якщо вже з'явилася можливість для остаточної позиції, я її не шукаю.

Ще розумні речі:

Для дедуплікації кожного блоку (рядки, де перша половина бітів однакові), я використовую бітвектор, що набагато швидше, ніж хештет, оскільки кінцеві довжини циклу не потребують хешування.

Також я пропускаю перші кроки перевірки циклу, оскільки знаю, що остаточний цикл не може бути коротшим, ніж цикл другий-останній.

Після багатьох профілів я зараз можу сказати, що майже весь час продуктивно використовується, тому для поліпшення звідси потрібно буде вдосконалити алгоритмічні вдосконалення. Я також перейшов на 32 бітні цілі числа, щоб заощадити трохи більше часу.

//extern crate cpuprofiler;
//use cpuprofiler::PROFILER;

extern crate bit_vec;
use bit_vec::BitVec;

use std::collections::HashSet;

fn cycle_len(num: u32, mask: u32, skip_steps: usize) -> usize {
    let mut left = num >> skip_steps;
    let mut mask = mask >> skip_steps;
    let mut steps = skip_steps;
    loop {
        left >>= 1;
        if left == (num & mask) {
            return steps;
        }
        mask >>= 1;
        steps += 1;
    }
}

fn all_cycles(size_log: usize) -> HashSet<Vec<usize>> {
    let mut set = HashSet::new();
    if size_log == 0 {
        set.insert(vec![]);
        return set;
    } else if size_log == 1 {
        set.insert(vec![0]);
        set.insert(vec![1]);
        return set;
    }
    let size: usize = 1 << size_log;
    let half_size: usize = 1 << size_log - 1;
    let shift_and_mask: Vec<(usize, u32)> = (1..size_log)
        .map(|subsize_log| {
            let subsize = 1 << subsize_log;
            (size - subsize, (1 << (subsize - 1)) - 1)
        })
        .collect();
    let size_mask = (1 << (size - 1)) - 1;
    for block in 0..(1 << (half_size - 1)) as u32 {
        let start: u32 = block << half_size;
        if block % 1024 == 0 {
            eprintln!(
                "{} ({:.2}%): {}",
                start,
                start as f64 / (1u64 << size - 1) as f64 * 100f64,
                set.len()
            );
        }
        let leader = {
            let mut cycles = Vec::new();
            for &(shift, mask) in &shift_and_mask {
                let subnum = start >> shift;
                cycles.push(cycle_len(subnum, mask, 0));
            }
            cycles
        };
        let &end = leader.last().unwrap();
        if (end..size).all(|count| {
            let mut new = leader.clone();
            new.push(count);
            set.contains(&new)
        })
        {
            continue;
        }
        let mut subset = BitVec::from_elem(size, false);
        for num in start..start + (1 << half_size) {
            subset.set(cycle_len(num, size_mask, end), true);
        }
        for (unique_cycle_len, _) in subset.into_iter().enumerate().filter(|x| x.1) {
            let mut new_l = leader.clone();
            new_l.push(unique_cycle_len);
            set.insert(new_l);
        }
    }
    set
}

fn main() {
    let size: f32 = std::env::args().nth(1).unwrap().parse().unwrap();
    let size_log = size.log2() as usize;
    //PROFILER.lock().unwrap().start("./my-prof.profile").unwrap();
    let cycles = all_cycles(size_log);
    //PROFILER.lock().unwrap().stop().unwrap();
    println!(
        "Number of distinct arrays of periods of bitstrings of length {} is {}",
        1 << size_log,
        cycles.len()
    );
}

Помістіть bit-vec = "0.4.4"у свій Cargo.toml

Якщо ви хочете запустити це, клонуйте це: github.com/isaacg1/cycle, Cargo build --releaseщоб скласти, а потім Cargo run --release 32запустити.


Схоже, eprintln потребує версії іржі після 0,16,0. Він працює, якщо я зміню його на println.

Ця відповідь дуже вражає. timeдає 27 користувачів секунди на моєму ноутбуці.

@Lembik, чому ти в такій старій версії іржі? Іржа 1,0 вийшла роки тому.
isaacg

Typo :) Я мав на увазі 1.16.0. blog.rust-lang.org/2017/03/16/Rust-1.16.html

Що стосується іржі-новачків, ви б не подумали прописати, як саме скласти код за допомогою вантажу?

4

Іржа , 16

use std::collections::HashSet;
use std::io;

fn main() {
	print!("Enter a pow of two:");
	let mut input_text = String::new();
    io::stdin()
        .read_line(&mut input_text)
        .expect("failed to read from stdin");

    let n_as_string = input_text.trim();
	match n_as_string.parse::<usize>() {
		Ok(n) => {
			let log2 = (n as f64).log(2_f64) as usize;
			if n != 1 << log2 {
				panic!("{} is not a power of two", n);
			}
			let count = compute_count_array(log2, n);
			println!("n = {} -> count = {}", n, count);
		}
		Err(_) => { panic!("{} is not a number", n_as_string); }
	}
}

fn compute_count_array(log2:usize, n: usize) -> usize {
	let mut z = HashSet::new();

	let mut s:Vec<bool> = vec!(false; n);
	loop {
		let mut y:Vec<usize> = vec!();
		for j in 0..log2+1 {
			let p = find_period(&s[0..1<<j]);
			y.push(p);
		}		
		z.insert(y);
		if !next(&mut s) {
			break;
		}
	}
	z.len()
}

#[inline]
fn find_period(s: &[bool]) -> usize {
	let n=s.len();
	let mut j=1;
	while j<n {
		if s[0..n-j] == s[j..n] {
			return j;
		}
		j+=1;
    }
	n
}	

#[inline]
fn next(s:&mut Vec<bool>) -> bool {
	if s[0] {
		s[0] = false;
		for i in 1..s.len() {
			if s[i] {
				s[i] = false;
			} else {
				s[i] = true;
				return true;
			}
		}
		return false
	} else {
		s[0] = true;
	}
	true
}

Спробуйте в Інтернеті!

Збірка: rustc -O <name>.rs

Рядок реалізований у вигляді вектора Bool.

  • nextФункція перебирати комбінації;

  • find_periodБере шматочок Bool і повертає період;

  • Робота compute_count_arrayвиконує завдання для кожної послідовності "двічі" кожної комбінації Bools.

Теоретично не очікується переповнення, поки не 2^nперевищить максимальне значення u64, тобто n > 64. Ця межа може бути охоплена дорогим тестом на s = [true, true, ..., true].

Погана новина: вона повертає 317 за n = 16, але я не знаю чому. Я не знаю, чи вдасться це зробити за десять хвилин за n = 32, оскільки значення Vec<bool>не оптимізоване для цього виду обчислень.

EDIT

  1. Мені вдалося зняти ліміт 64 для n. Тепер він не завершиться nзбоєм, поки не перевищить максимальне ціле число.

  2. Я з’ясував, чому попередній код повернув 317 для n=32. Я рахував набори періодів, а ні масиви періодів. Було три масиви з однаковими елементами:

    [1, 2, 3, 3, 8] -> {1, 2, 3, 8}
    [1, 2, 3, 8, 8] -> {1, 2, 3, 8}
    [1, 1, 3, 3, 7] -> {1, 3, 7}
    [1, 1, 3, 7, 7] -> {1, 3, 7}
    [1, 1, 3, 3, 8] -> {1, 3, 8}
    [1, 1, 3, 8, 8] -> {1, 3, 8}
    

Зараз це працює. Він все ще повільний, але працює.


Тут усі 320 для n = 16 bpaste.net/show/3664e25ebc01 .

1
@Lembik Я знайшов пояснення до 317 завдяки вашому списку.
jferard

2

C - 16

Він не вдається при значеннях, що перевищують 16 cuz переповнення.

Я поняття не маю, наскільки швидко це працює, тому що він працює на хроніці, який працює на repl.it.

Просто реалізує запитання під час його читання, проходить всі рядки бітів, обчислює масиви періодів та перевіряє, чи вони вже були підраховані.

#include "stdio.h"
#include <stdbool.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

int per(int s[], int l) {
  int period = 0;
  while (1) {
    period++;

    bool check = 1;
    int i;
    for (i=0; i<l-period; i++) {
      if (s[i]!=s[i+period]) {
        check = 0;
        break;
      }
    }
    if (check) {
      return period;
    }
  }
}

bool perar(int* s, int l, int* b, int i) {
  int n = 1;
  int j=0;
  while (n<=l) {
    b[i*l+j] = per(s, n);
    n=n<<1;
    j++;
  }

  for (j=0;j<i;j++) {
    int k;
    bool check = 1;
    for(k=0; k<l; k++) {
      if (b[j*l+k] != b[i*l+k]) {
        check = 0;
        break;
      }
    }
    if (check) {
      return 0;
    }
  }
  return 1;
}

int main(int argc, char* argv[]) {
  int n;
  scanf("%d", &n);
  puts("Running...");
  int i;
  int c = 0;
  int* a = malloc(n*sizeof(int));
  int m=pow(2, n);
  int* b = malloc(m*n*sizeof(int));
  for (i=0; i<m; i++) {
    int j;
    for (j=0; j<n; j++) {
      a[j] = (i>>j)&1;
    }
    c+=perar(a, n, b, i);
  }
  printf("Answer: %d\n", c);
  return 0;
}

Просто компілюйте його з gcc тощо.


FYI - тоді було помилково, 16коли код було змінено так, що два mallocs malloc(...int*))та ...**відповідно 16надруковано, Answer: 320як очікувалося, однак 32надруковано Answer: 0(і досить швидко).
Джонатан Аллан

@JonathanAllan виправив матеріал, щойно зробив b a int *.
Малтісен

@JonathanAllan 32 річ, тому що 2 ** 32 переповнює int. Також у мене буде пробіг пам'яті.
Мальтісен

@ThePirateBay я зробив я і м довгий, і це просто segfaults, коли я намагаюся 32. repl.it/JwJl/2 Я здогадуюсь, що у нього не вистачає пам'яті.
Малтісен

@Maltysen Здається, що це segfault, тому що ви щось зіпсували при розподілі / розборі, а не через брак пам’яті. Я отримав segfault для, n = 8але після того, як результат буде надрукований, це означає, що стек пошкоджений. Можливо, ви десь пишете поза виділеними блоками пам'яті.

2

Хаскелл

import qualified Data.Set as S
import Data.Bits

period :: Int -> Int -> Int
period num bits = go (bits-2) (div prefix 2) (clearBit prefix $ bits-1)
  where
  prefix = (2^bits-1) .&. num
  go p x y
    | x == y    = p
    | otherwise = go (p-1) (div x 2) (clearBit y p)

allPeriods :: Int ->  [[Int]]
allPeriods n = map periods [0..div(2^n)2-1]
  where
  periods num = map (period num) powers
  powers = takeWhile (<=n) $ iterate (*2) 2

main = readLn >>= print . S.size . S.fromList . allPeriods

Компілювати з ghc -O2.Спробуйте в Інтернеті!

Моє 6-річне обладнання для ноутбука працює менше ніж 0,1 сек n=16. n=32займає 99 92 хв., тому я знижую коефіцієнт 9 або 10. Я намагався кешувати періоди в таблиці пошуку, тому мені не доведеться перераховувати їх знову і знову, але це швидко втрачає пам'ять на моїй 4 Гб машині.


Незважаючи на те, що він є коефіцієнтом 10 відключений, ваш код дуже добре виглядає.

@Lembik. Дякую. Я просто намагаюся покращити: вищевказаний код обчислює періоди для підрядків довжиною 1, але це зовсім непотрібно. Крім того, що не потрібно їх обчислювати, це також економить час, коли знаходяться унікальні масиви періодів, оскільки всі вони на один елемент коротші.
німі

@Lembik: опускання підрядів довжиною 1 зберігає близько 7 хв для n = 32. Ще занадто довго.
німі

Існує швидкий лінійний алгоритм для обчислення періоду, який може допомогти.

Невже ви не можете створити таблицю пошуку розміром 2 ^ 16? Це не здається занадто великим.

1

Python 2 (PyPy), 16

import sys
import math
def do(n):
 masks=[]
 for i in range(n):
  masks+=[(1<<((2<<i)-1))-1]
 s=set()
 bits=1<<n
 for i in xrange(1<<bits):
  r=[0,]*n
  for j in range(len(masks)):
   mask=masks[j]
   k,c=i>>bits-(2<<j),1
   d=k>>1
   while k&mask^d:
    d>>=1
    mask>>=1
    c+=1
   r[j]=c
  s|={tuple(r)}
 return len(s)
print do(int(math.log(int(sys.argv[1]),2)))

: | чому 32 роки потрібно так довго
лише

Я знаю, що можу пропустити половину з них, але IDK як: /
ASCII лише

Здається, ваш код видає для мене лише "None". Як ти це працюєш? osboxes@osboxes:~/python$ python ascii_user.py 16 None

лайно вибачте, це насправді не те, що я запускаю
лише ASCII

@Lembik виправлено зараз
лише ASCII

1

[C ++], 32, 4 хв

#include <iostream>
#include <vector>

typedef unsigned int u;
template<typename T, typename U>
u Min(T a, U b) {
    return a < b ? a : b;
}

template<typename T, typename U>
u Max(T a, U b) {
    return a > b ? a : b;
}

u Mask(int n) {
    if (n < 0) n = 0;
    return ~((u)(-1) << n);
}
u MASKS[32];

inline u Rshift(u v, int n) {
    return n < 0 ? v >> (-1*n)
    : n > 0 ? v << n
    : n;
}

int GetNextPeriodId(u pattern, int pattern_width, int prior_id) {
    int period = (prior_id % (pattern_width>>1)) + 1;
    int retval = prior_id * pattern_width;

    for (; period < pattern_width; period+=1) {
        u shift = pattern >> period;
        int remainder = pattern_width-period;
        u mask = MASKS[period];

        for (;remainder >= period && !((pattern ^ shift) & mask);
             shift >>= period, remainder -= period);

        if (remainder > period) continue;
        if (remainder == 0 || !((pattern ^ shift) & MASKS[remainder])) {
            retval += (period-1);
            break;
        }
    }
    if (period == pattern_width) {
        retval += pattern_width-1;
    }
    return retval;
}

int ParseInput(int argc, char** argv) {
    if (argc > 1) {
        switch(atoi(argv[1])) {
            case 1:
                return 1;
            case 2:
                return 2;
            case 4:
                return 4;
            case 8:
                return 8;
            case 16:
                return 16;
            case 32:
                return 32;
            default:
                return 0;
        }
    }
    return 0;
}

void PrintId(u id, int patternWidth) {
    for(;patternWidth > 0; id /= patternWidth, patternWidth >>= 1) {
        std::cout << (id % patternWidth)+1 << ",";
    }
    std::cout << std::endl;
}

int TestAndSet(std::vector<bool>& v, int i) {
    int retval = v[i] ? 0 : 1;
    v[i] = true;
    return retval;
}

std::vector<bool> uniques(1<<15);
int uniqueCount = 0;

void FillUniques(u i, int id, int target_width, int final_width) {
    int half_size = target_width / 2;
    u end = 1u<<(half_size-1);
    u mask = MASKS[half_size];
    u lowers[] = { i, (~i)&mask };
    for (u j = 0ul; j < end; j++) {
        u upper = j << half_size;
        u patterns[] = { (upper|lowers[0]), (upper|lowers[1]) };
        for (int k=0; k < sizeof(patterns)/sizeof(patterns[0]); k+=1) {
            int fid = GetNextPeriodId(patterns[k], target_width, id);
            if (target_width != final_width) {
                FillUniques(patterns[k], fid, target_width*2, final_width);
            } else {
                if (TestAndSet(uniques, fid)) {
                    uniqueCount += 1;
                }
            }
        }
    }
}

int main(int argc, char** argv) {
    for (int i = 0; i < 32; i++) {
        MASKS[i] = Mask(i);
    }
    int target_width = 32; // ParseInput(argc, argv);
    if (!target_width) {
        std::cout << "Usage: " << argv[0] << " [1|2|4|8|16|32]" << std::endl;
        return 0;
    }
    if (target_width == 1) {
        std::cout << 1 << std::endl;
        return 0;
    }
    FillUniques(0, 0, 2, target_width);
    std::cout << uniqueCount << std::endl;
    return 0;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.