Подтвердить что ты не робот

Решение обратного отслеживания для упражнений по программированию (фитинговые трубы)

Я просматриваю проблему программирования из конкурса локального программирования.

Вы можете загрузить проблему здесь (pdf). Это в голландском, но фотографии помогут понять это.

Вы получаете сетку m * m в качестве ввода с некоторыми трубами и некоторыми отсутствующими точками (вопросительные знаки). Остальные трубы должны быть помещены в сетку, чтобы они соединялись с другими.

Каждая труба представлена ​​в виде буквы (см. рисунок на стр. 2). Буква "A" имеет значение 1, "B" имеет значение 2,..

Я попытался решить его с помощью backtracking (он пока не работает). Но так как сетка может быть 10x10, это будет слишком медленным. Может ли кто-нибудь предложить лучшее (более быстрое) решение/подход?

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;

#define sz(a) int((a).size())
#define pb push_back

int m, found;
string letters;
vector<int> done;
vector<string> a;

int ok(char letter, int c, int r)
{
    int val = letter - 'A' + 1;

    //checking if no side goes outside
    if (r == 0 && (val & 1))
        return 0;
    if (r == m - 1 && (val & 4))
        return 0;
    if (c == 0 && (val & 8))
        return 0;
    if (c == m - 1 && (val & 2))
        return 0;

    //check if the side is connected the other pipe on the grid
    if (r > 0 && a[r - 1][c] != '?' && (a[r - 1][c] & 4) && !(val & 1))
        return 0;
    if (c > 0 && a[r][c - 1] != '?' && (a[r][c - 1] & 2) && !(val & 8))
        return 0;
    if (r < m - 1 && a[r + 1][c] != '?' && (a[r + 1][c] & 1) && !(val & 4))
        return 0;
    if (c < m - 1 && a[r][c + 1] != '?' && (a[r][c + 1] & 8) && !(val & 2))
        return 0;

    return 1;
}

void solve(int num_placed, int pos)
{
    if (found) return;

    //done
    if (num_placed == sz(letters)) {
        for (int i = 0; i < m; ++i)
            cout << a[i] << endl;
        found = 1;
        return;
    }

    int c = pos % m;
    int r = pos / m;
    if (a[r][c] != '?')
        solve(num_placed, pos + 1);

    //try all the pipes on this position
    for (int i = 0; i < sz(letters); ++i) {
        if (!done[i] && ok(letters[i], c, r)) {
            a[r][c] = letters[i];
            done[i] = 1;
            solve(num_placed + 1, pos + 1);
            done[i] = 0;
            a[r][c] = '?';
        }
    }
}

int main()
{
    freopen("input.txt", "r", stdin);

    int n;
    cin >> n;

    while (n--) {
        cin >> m;
        cin >> letters;

        cout << m << endl;
        a.clear();
        for (int i = 0; i < m; ++i) {
            string line;
            cin >> line;
            a.pb(line);
        }

        done = vector<int>(sz(letters), 0);

        found = 0;
        solve(0, 0);
    }

    return 0;
}
4b9b3361

Ответ 1

исходный ответ

Вам нужно написать весь код самостоятельно или вы заинтересованы в изучении других инструментов? потому что я бы предложил посмотреть на распространение ограничений/линейное программирование. у вас уже есть множество ограничений по границам, - что внешние края не могут иметь трубы, а также внутренние края, поэтому я думаю, что это будет работать достаточно эффективно. Кроме того, ограничения выглядят так, как будто они будут простыми равенствами, поэтому его довольно легко настроить.

У меня нет достаточного опыта для этого, чтобы дать более подробную информацию здесь (хотя, если у меня будет время на следующей неделе, я могу дать ему шанс в какой-то момент), но если такого рода вещи интересны там немного больше background in еще один ответ, который я написал некоторое время назад.

ps интересный вопрос; спасибо за публикацию этого.

[edit: если вы не можете использовать другие библиотеки, тогда вы можете сами распространять ограничение. есть замечательная статья norvig которая показывает, как это сделать для судоку. я бы настоятельно рекомендовал прочитать это - я думаю, вы увидите, как переносить эти методы, даже если это судоку и питон.]

обновленный ответ (2012-04-06 - обновлено с помощью ссылок на блоги, старые комментарии были ошибочными)

поиск по глубине, где следующая пустая ячейка заполняется каждой доступной, согласованной черепицей, а проверка согласованности включает в себя как ограничения границ (без труб от края), так и ближайших соседей. У меня есть неоптимизированная реализация в clojure, которая решит меньший пример примерно в 0,4 мс (1000 в 360 мс после прогрева JVM), и больше в 3 мс (cedric van goethem сообщает 1 мс для оптимизированной, но все же OO-java-реализации, которая кажется разумным). он может решить головоломку 10x10 (концентрические круги трубок без начальных намеков) за 12 секунд.

Я также потратил время на "умный" подход, который отслеживает ограничения на каждую ячейку, как и на норвигской бумаге выше. а затем я попытался использовать choco. все это описано более подробно в сообщениях в блоге здесь (у меня было больше деталей в обновлении этого ответа, но они были основаны на глючный код - в блоге больше, более качественная информация). источник также доступен для загрузки.

общий вывод из всего этого заключался в том, что прямой поиск хорош до 10x10. после этого больше может помочь больше умений, но это трудно убедить, потому что генерировать тестовые примеры трудно (они имеют тенденцию быть неоднозначными, даже когда много ячеек даются).

Ответ 2

Хорошая проблема. Я нашел решение в O (n · m · 8 ^ m), которое кажется достаточным.

  • Сосредоточьтесь на границе между первой и второй строками. Существует 2 м-м возможностей (исходящая линия или нет, для каждой стороны). Это будет верхняя пограничная линия, нижняя граница линии не будет соединения с каждой стороны.

  • Для каждой пары нижней пограничной линии и верхней граничной линии (которая должна быть 2 ^ m · 2 ^ m = 4 ^ m пар), вычислите каждую строку, которая вписывается. Если вы пришли слева, вы даны левая сторона, верхняя и нижняя стороны, поэтому у вас есть только 2 возможности. Если плитка, на которую вы смотрите, зафиксирована на карте, убедитесь, что она вписывается и прерывается в противном случае. Назовите это рекурсивно, и вы получите 2 ^ m строк по одному или 4 ^ m * 2 ^ m = 8 ^ м.

  • Пока последним шагом была чистая грубая сила, на этот раз мы используем DP. Защитите кортеж (граница, используемый кирпич, строка) в массиве массивов. array [0] будет содержать один кортеж (пустая граница, без использования кирпича, ничего). array [n] содержит все 8 ^ m сгенерированных строк в строке n (начиная с 1) в сочетании с каждым элементом в массиве [n-1], в котором он подходит (т.е. граница элемента такая же, как нижняя граница строки ) Обратите внимание, что если вы смело совмещаете это условие с циклом на шаге 2, это ничего не стоит.

  • Удалите все кортежи, которым требуется больше кирпичей одного вида, чем доступно, и отсортируйте массив. Затем перейдите к шагу 2 до тех пор, пока не будут обработаны все строки.

  • Если вы закончили, проверьте массив [n] для кортежа (строка с пустой границей, все кирпичи, строка). Поскольку описание вашей задачи означает, что она существует, распечатайте ее строку. Затем посмотрите на нижнюю границу строки и найдите ее в массиве [n-1] и распечатайте ее тоже и т.д.

Надеюсь, ты понимаешь мой английский.