Тік-так-носок з лише хрестами якомога швидше


10

Відповідно до прохання Луки та доповнення Пітера Тейлора до цього виклику.

Вступ

Всім відома гра tic-tac-toe, але в цьому виклику ми збираємося запровадити невеликий поворот. Ми будемо використовувати лише хрестики . Перша людина, яка поставить три хрести підряд, програє. Цікавий факт, що максимальна кількість хрестів, перш ніж хтось програє, дорівнює 6 :

X X -
X - X
- X X

Це означає, що для дошки 3 х 3 максимальна сума - 6 . Отже, для N = 3 нам потрібно вивести 6.

Інший приклад для N = 4 або 4 x 4 плати:

X X - X
X X - X
- - - -
X X - X

Це оптимальне рішення, ви можете бачити, що максимальна кількість хрестів дорівнює 9 . Оптимальним рішенням для плати 12 х 12 є:

X - X - X - X X - X X -
X X - X X - - - X X - X
- X - X - X X - - - X X
X - - - X X - X X - X -
- X X - - - X - - - - X
X X - X X - X - X X - -
- - X X - X - X X - X X
X - - - - X - - - X X -
- X - X X - X X - - - X
X X - - - X X - X - X -
X - X X - - - X X - X X
- X X - X X - X - X - X

Це призводить до 74 .

Завдання

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

Тестові справи

N     Output
1       1
2       4
3       6
4       9
5       16
6       20
7       26
8       36
9       42
10      52
11      64
12      74
13      86
14      100
15      114

Більше інформації можна знайти на https://oeis.org/A181018 .

Правила

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

Подання:

  • feersum (C ++ 11): 28с
  • Пітер Тейлор (Java): 14м 31с


Чи це не просто дурман другого питання , наскільки я можу сказати, ви просто змінили умову виграшу?
Блакитний

1
@muddyfish Хоча сам виклик виглядає однаково, я можу гарантувати, що підхід до цього виклику сильно відрізняється від мого іншого виклику.
Аднан

3
@muddyfish Відповідна мета дискусія. "Просто зміна умови виграшу" може бути суттєвою зміною для виклику. Хоча не має сенсу розміщувати код-гольф , найшвидший алгоритм та найшвидший код для кожного можливого виклику, є деякі випадки, коли вивчення проблеми з двох кутів може додати велику цінність сайту. Я думаю, що це такий випадок.
Мартін Ендер

1
Чудовий виклик! (+1)

Відповіді:


5

C ++ 11, 28с

Тут також використовується динамічний підхід до програмування на основі рядків. Для запуску з аргументом 13 мені знадобилося 28 секунд. Мій улюблений трюк - це nextфункція, яка використовує трохи біт-башти для пошуку лексикографічного розташування наступного рядка, що задовольняє маску та правило "не-3-в-ряді".

Інструкції

  1. Встановіть останню версію MinGW-w64 з потоками SEH та Posix
  2. Складіть програму g++ -std=c++11 -march=native -O3 <filename>.cpp -o <executable name>
  3. Бігайте з <executable name> <n>
#include <vector>
#include <stddef.h>
#include <iostream>
#include <string>

#ifdef _MSC_VER
#include <intrin.h>
#define popcount32 _mm_popcnt_u32
#else
#define popcount32 __builtin_popcount
#endif


using std::vector;

using row = uint32_t;
using xcount = uint8_t;

uint16_t rev16(uint16_t x) { // slow
    static const uint8_t revbyte[] {0,128,64,192,32,160,96,224,16,144,80,208,48,176,112,240,8,136,72,200,40,168,104,232,24,152,88,216,56,184,120,248,4,132,68,196,36,164,100,228,20,148,84,212,52,180,116,244,12,140,76,204,44,172,108,236,28,156,92,220,60,188,124,252,2,130,66,194,34,162,98,226,18,146,82,210,50,178,114,242,10,138,74,202,42,170,106,234,26,154,90,218,58,186,122,250,6,134,70,198,38,166,102,230,22,150,86,214,54,182,118,246,14,142,78,206,46,174,110,238,30,158,94,222,62,190,126,254,1,129,65,193,33,161,97,225,17,145,81,209,49,177,113,241,9,137,73,201,41,169,105,233,25,153,89,217,57,185,121,249,5,133,69,197,37,165,101,229,21,149,85,213,53,181,117,245,13,141,77,205,45,173,109,237,29,157,93,221,61,189,125,253,3,131,67,195,35,163,99,227,19,147,83,211,51,179,115,243,11,139,75,203,43,171,107,235,27,155,91,219,59,187,123,251,7,135,71,199,39,167,103,231,23,151,87,215,55,183,119,247,15,143,79,207,47,175,111,239,31,159,95,223,63,191,127,255};
    return uint16_t(revbyte[x >> 8]) | uint16_t(revbyte[x & 0xFF]) << 8;
}

// returns the next number after r that does not overlap the mask or have three 1's in a row
row next(row r, uint32_t m) {
    m |= r >> 1 & r >> 2;
    uint32_t x = (r | m) + 1;
    uint32_t carry = x & -x;
    return (r | carry) & -carry;
}

template<typename T, typename U> void maxequals(T& m, U v) {
    if (v > m)
        m = v;
}

struct tictac {
    const int n;
    vector<row> rows;
    size_t nonpal, nrows_c;
    vector<int> irow;
    vector<row> revrows;

    tictac(int n) : n(n) { }

    row reverse(row r) {
        return rev16(r) >> (16 - n);
    }

    vector<int> sols_1row() {
        vector<int> v(1 << n);
        for (uint32_t m = 0; !(m >> n); m++) {
            auto m2 = m;
            int n0 = 0;
            int score = 0;
            for (int i = n; i--; m2 >>= 1) {
                if (m2 & 1) {
                    n0 = 0;
                } else {
                    if (++n0 % 3)
                        score++;
                }
            }
            v[m] = score;
        }
        return v;
    }

    void gen_rows() {
        vector<row> pals;
        for (row r = 0; !(r >> n); r = next(r, 0)) {
            row rrev = reverse(r);
            if (r < rrev) {
                rows.push_back(r);
            } else if (r == rrev) {
                pals.push_back(r);
            }
        }
        nonpal = rows.size();
        for (row r : pals) {
            rows.push_back(r);
        }
        nrows_c = rows.size();
        for (int i = 0; i < nonpal; i++) {
            rows.push_back(reverse(rows[i]));
        }
        irow.resize(1 << n);
        for (int i = 0; i < rows.size(); i++) {
            irow[rows[i]] = i;
        }
        revrows.resize(1 << n);
        for (row r = 0; !(r >> n); r++) {
            revrows[r] = reverse(r);
        }
    }

    // find banned locations for 1's given 2 above rows
    uint32_t mask(row a, row b) {
        return ((a & b) | (a >> 1 & b) >> 1 | (a << 1 & b) << 1) /*& ((1 << n) - 1)*/;
    }

    int calc() {
        if (n < 3) {
            return n * n;
        }
        gen_rows();
        int tdim = n < 5 ? n : (n + 3) / 2;
        size_t nrows = rows.size();
        xcount* t = new xcount[2 * nrows * nrows_c]{};
#define tb(nr, i, j) t[nrows * (nrows_c * ((nr) & 1) + (i)) + (j)]

        // find optimal solutions given 2 rows for n x k grids where 3 <= k <= ceil(n/2) + 1

        {
            auto s1 = sols_1row();
            for (int i = 0; i < nrows_c; i++) {
                row a = rows[i];
                for (int j = 0; j < nrows; j++) {
                    row b = rows[j];
                    uint32_t m = mask(b, a) & ~(1 << n);
                    tb(3, i, j) = s1[m] + popcount32(a << 16 | b);
                }
            }
        }
        for (int r = 4; r <= tdim; r++) {
            for (int i = 0; i < nrows_c; i++) {
                row a = rows[i];
                for (int j = 0; j < nrows; j++) {
                    row b = rows[j];
                    bool rev = j >= nrows_c;
                    auto cj = rev ? j - nrows_c : j;
                    uint32_t m = mask(a, b);
                    for (row c = 0; !(c >> n); c = next(c, m)) {
                        row cc = rev ? revrows[c] : c;
                        int count = tb(r - 1, i, j) + popcount32(c);
                        maxequals(tb(r, cj, irow[cc]), count);
                    }
                }
            }
        }
        int ans = 0;
        if (tdim == n) { // small sizes
            for (int i = 0; i < nrows_c; i++) {
                for (int j = 0; j < nrows; j++) {
                    maxequals(ans, tb(n, i, j));
                }
            }
        } else {
            int tdim2 = n + 2 - tdim;
            // get final answer by joining two halves' solutions down the middle
            for (int i = 0; i < nrows_c; i++) {
                int apc = popcount32(rows[i]);
                for (int j = 0; j < nrows; j++) {
                    row b = rows[j];
                    int top = tb(tdim2, i, j);
                    int bottom = j < nrows_c ? tb(tdim, j, i) : tb(tdim, j - nrows_c, i < nonpal ? i + nrows_c : i);
                    maxequals(ans, top + bottom - apc - popcount32(b));
                }
            }
        }
        delete[] t;
        return ans;
    }
};


int main(int argc, char** argv) {
    int n;
    if (argc < 2 || (n = std::stoi(argv[1])) < 0 || n > 16) {
        return 1;
    }
    std::cout << tictac{ n }.calc() << '\n';
    return 0;
}

7

Ява, 14м 31с

Це, по суті, програма, яку я опублікував в OEIS після її використання для розширення послідовності, тому це хороша орієнтація для того, щоб інші люди били. Я змінив його, щоб прийняти розмір плати як перший аргумент командного рядка.

public class A181018 {
    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
        System.out.println(calc(n));
    }

    private static int calc(int n) {
        if (n < 0) throw new IllegalArgumentException("n");
        if (n < 3) return n * n;

        // Dynamic programming approach: given two rows, we can enumerate the possible third row.
        // sc[i + rows.length * j] is the greatest score achievable with a board ending in rows[i], rows[j].
        int[] rows = buildRows(n);
        byte[] sc = new byte[rows.length * rows.length];
        for (int j = 0, k = 0; j < rows.length; j++) {
            int qsc = Integer.bitCount(rows[j]);
            for (int i = 0; i < rows.length; i++) sc[k++] = (byte)(qsc + Integer.bitCount(rows[i]));
        }

        int max = 0;
        for (int h = 2; h < n; h++) {
            byte[] nsc = new byte[rows.length * rows.length];
            for (int i = 0; i < rows.length; i++) {
                int p = rows[i];
                for (int j = 0; j < rows.length; j++) {
                    int q = rows[j];
                    // The rows which follow p,q cannot intersect with a certain mask.
                    int mask = (p & q) | ((p << 2) & (q << 1)) | ((p >> 2) & (q >> 1));
                    for (int k = 0; k < rows.length; k++) {
                        int r = rows[k];
                        if ((r & mask) != 0) continue;

                        int pqrsc = (sc[i + rows.length * j] & 0xff) + Integer.bitCount(r);
                        int off = j + rows.length * k;
                        if (pqrsc > nsc[off]) nsc[off] = (byte)pqrsc;
                        if (pqrsc > max) max = pqrsc;
                    }
                }
            }

            sc = nsc;
        }

        return max;
    }

    private static int[] buildRows(int n) {
        // Array length is a tribonacci number.
        int c = 1;
        for (int a = 0, b = 1, i = 0; i < n; i++) c = a + (a = b) + (b = c);

        int[] rows = new int[c];
        int i = 0, j = 1, val;
        while ((val = rows[i]) < (1 << (n - 1))) {
            if (val > 0) rows[j++] = val * 2;
            if ((val & 3) != 3) rows[j++] = val * 2 + 1;
            i++;
        }

        return rows;
    }
}

Зберегти в A181018.java; скласти як javac A181018.java; бігти як java A181018 13. На моєму комп'ютері для цього входу потрібно близько 20 хвилин. Напевно, варто було б його паралелізувати.


Скільки часу ви це виконуєте? Ви все ще додаєте терміни до ОЕІС, я бачу.
mbomb007

1
@ mbomb007, я ще не працюю. Як ви можете сказати з дат OEIS, на те, щоб пробігти кілька днів n=16; Я екстраполював, що це займе близько місяця n=17, тому я не намагався запустити це. Використання пам'яті також стало великою неприємністю. (PS В даний час я використовую 2 з 4 ядер для виклику програмування, що не є PPCG: azspcs.com/Contest/Tetrahedra/Standings )
Пітер Тейлор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.