Fuochi d'artificio di fusione


13

Panoramica

Dato un elenco di fuochi d'artificio a-ze orari 3-78, disponili con micce per accenderli tutti al momento giusto.

Una riga di input viene fornita come lettere e numeri separati da spazi:

a 3 b 6 c 6 d 8 e 9 f 9

Che esempio mostra che firework anecessità di luce alla volta 3, be csia a 6, da 8, con ee fsia a 9. Ogni riga corrisponde a una mappa.

L'output è una mappa dei fusibili / fuochi d'artificio per ogni linea, usando i simboli |-per mostrare i fusibili e le lettere per mostrare i fuochi d'artificio.

Una -miccia si collega a micce e fuochi d'artificio direttamente a sinistra / destra di essa, mentre una |miccia si collega a quelle sopra / sotto. Ad esempio, i fusibili non|| sono collegati e lo sono .-|

Ad esempio, due possibili risposte a quanto sopra sono:

---a        ---------f
  |         |||   ||
  |-c       |||   de
--|--d      a||
| b |        |c
f   e        b

Tutte le mappe dei fusibili dovrebbero iniziare con una singola -nell'angolo in alto a sinistra. Questo è il punto in cui si accende la miccia. Ogni personaggio di miccia impiega un secondo per bruciare. Come puoi vedere, aviene raggiunto in tre secondi in entrambi i diagrammi, bin sei, ecc.

Ora, entrambe le mappe fornite sopra sono valide per l'input fornito, ma una è chiaramente più efficiente. Quello sinistro usa solo 13 unità di miccia, mentre quello destro ne prende 20.

I fusibili non bruciano attraverso i fuochi d'artificio! Quindi, per input a 3 b 5, questo non è valido:

---a--b

Sfida

Il tuo obiettivo è ridurre al minimo la quantità di miccia utilizzata in tutti i casi di test. Il punteggio è molto semplice, le unità totali di miccia utilizzate.

Se non è possibile produrre una mappa per un caso di test, che si tratti di un caso impossibile o meno, il punteggio per quel caso è la somma di tutti i tempi (41 per l'esempio sopra).

In caso di pareggio, il punteggio viene modificato in modo da vincere le mappe più compatte . Il punteggio del tiebreak è l' area del riquadro di delimitazione di ciascuna mappa. Cioè, la lunghezza della linea più lunga è il numero di righe. Per le mappe "impossibili" questo è il quadrato del numero più grande (81 per l'esempio sopra).

Nel caso in cui i contributi associno entrambi questi metodi di punteggio, il pareggio passa alla voce / modifica precedente.

Il tuo programma deve essere deterministico, a scopo di verifica.

Casi test

Ci sono 250 casi di test, situati qui . Ognuno ha tra 4 e 26 fuochi d'artificio. Il tempo minimo di miccia per un fuoco d'artificio è 3. I fuochi d'artificio in ogni caso sono "ordinati" per tempo e lettera, il che significa bche non si accenderanno mai prima a .

Quando pubblichi, ti preghiamo di includere il tuo programma completo, il tuo punteggio totale e la mappa risultante per (almeno) il primo caso di test indicato nel file:

a 6 b 8 c 11 d 11 e 11 f 11 g 12 h 15 i 18 j 18 k 21 l 23 m 26 n 28 o 28 p 30 q 32 r 33 s 33 t 34 

Possono arbitrariamente molti fuochi d'artificio spegnersi contemporaneamente?
Ingo Bürk,

Fondamentalmente sì. Non ho cercato la più grande istanza di questo nei miei casi di test, ma so che sono almeno quattro. Il tempo tra due micce è rand.nextInt(5)%4, quindi una probabilità del 40% 0e il 20% per ciascuno 1,2,3.
Geobits

Solo un suggerimento: userei un '+' per il punto in cui i fusibili si collegano o cambiano direzione, il che renderebbe la grafica in uscita IMHO molto più intuitiva!
Flawr

@flawr lo permetterò, a condizione che sia fatto in modo da non cambiare il punteggio. Ad esempio, -+-al posto di ---non collegare automaticamente i fuochi d'artificio sopra / sotto, ci deve essere ancora un |sopra / sotto per collegarlo a un fuoco d'artificio. -+-al posto di -|-va bene così com'è.
Geobits,

Tutti i casi di test sono risolvibili? Ad esempio, se ci fossero cinque o più fuochi d'artificio per spegnersi al momento 3, non credo che si possano adattare tutti abbastanza vicino all'inizio. Allo stesso modo, potresti essere in grado di adattarli tutti, ma potrebbero bloccare la strada verso l'esterno per i successivi fuochi d'artificio.
Martin Ender,

Risposte:


3

C ++

Lunghezza totale: 9059, Area totale: 27469, Guasti: 13.

Nota: il punteggio include penalità di fallimento.


Uscita campione:

a 6 b 8 c 11 d 11 e 11 f 11 g 12 h 15 i 18 j 18 k 21 l 23 m 26 n 28 o 28 p 30 q 32 r 33 s 33 t 34 
------ae  
     | |  
     |---c
     b||-g
      |d| 
      f | 
    i---| 
  k---| h 
   |  j   
   |---m  
   l  | t 
     o-n| 
      |s-r
      |-| 
      p q 
Length: 39, Area: 150.

a 6 b 6 c 6 d 6 e 6 f 6 g 6 h 8 i 9 j 9 k 9 l 12 m 12 n 13 o 14 p 15 q 15 r 15 s 17 t 17 u 17 v 17 w 17 x 20 y 23 z 26 
------a  n|--w 
|d-||---k|-o|  
| g|b  |--m --x
|-|c    ||--r| 
||f     l|-q | 
||--j u--|--s|-
e|-i    |p|  y|
 h      v t  z-
Length: 56, Area: 120.

Uscita completa: http://pastebin.com/raw.php?i=spBUidBV


Non ti piacciono le soluzioni a forza bruta? Questo è un po 'più di un semplice algoritmo di backtracking: il nostro instancabile lavoratore si sposta sulla mappa, posizionando micce e fuochi d'artificio, se necessario, testando tutte le possibili mosse in qualsiasi momento. Bene, quasi --- limitiamo l'insieme di mosse e abbandoniamo precocemente gli stati non ottimali in modo che non ci voglia insopportabilmente lungo (e, in particolare, in modo che termini). Si presta particolare attenzione a non creare cicli o involontari percorsi e di non tornare allo stesso modo in cui siamo venuti, quindi è garantito che non visiteremo lo stesso stato due volte. Anche così, trovare una soluzione ottimale può richiedere del tempo, quindi alla fine rinunciamo a ottimizzare una soluzione se impiega troppo tempo.

Questo algoritmo ha ancora qualche margine. Per prima cosa, è possibile trovare soluzioni migliori aumentando i FRUSTRATIONparametri. Non c'è un bancomat della concorrenza, ma questi numeri possono essere aumentati se e quando ...

Compilare con: g++ fireworks.cpp -ofireworks -std=c++11 -pthread -O3.

Corri con: ./fireworks.

Legge l'input da STDIN e scrive l'output su STDOUT (possibilmente non funzionante).

/* Magic numbers */
#define THREAD_COUNT 2
/* When FRUSTRATION_MOVES moves have passed since the last solution was found,
 * the last (1-FRUSTRATION_STATES_BACKOFF)*100% of the backtracking states are
 * discarded and FRUSTRATION_MOVES is multiplied by FRUSTRATION_MOVES_BACKOFF.
 * The lower these values are, the faster the algorithm is going to give up on
 * searching for better solutions. */
#define FRUSTRATION_MOVES 1000000
#define FRUSTRATION_MOVES_BACKOFF 0.8
#define FRUSTRATION_STATES_BACKOFF 0.5

#include <iostream>
#include <vector>
#include <algorithm>
#include <utility>
#include <thread>
#include <mutex>
#include <string>
#include <sstream>
#include <cassert>

using namespace std;

/* A tile on the board. Either a fuse, a firework, an empty tile or an
 * out-of-boudns tile. */
struct tile {
    /* The tile's value, encoded the "obvious" way (i.e. '-', '|', 'a', etc.)
     * Empty tiles are encoded as '\0' and OOB tiles as '*'. */
    char value;
    /* For fuse tiles, the time at which the fuse is lit. */
    int time;

    operator char&() { return value; }
    operator const char&() const { return value; }

    bool is_fuse() const { return value == '-' || value == '|'; }
    /* A tile is vacant if it's empty or OOB. */
    bool is_vacant() const { return !value || value == '*'; }

    /* Prints the tile. */
    template <typename C, typename T>
    friend basic_ostream<C, T>& operator<<(basic_ostream<C, T>& os,
                                            const tile& t) {
        return os << (t.value ? t.value : ' ');
    }
};
/* Fireworks have the same encoding as tiles. */
typedef tile firework;
typedef vector<firework> fireworks;

/* The fuse map. It has physical dimensions (its bounding-box) but is
 * conceptually infinite (filled with empty tiles.) */
class board {
    /* The tiles, ordered left-to-right top-to-bottom. */
    vector<tile> p_data;
    /* The board dimensions. */
    int p_width, p_height;
    /* The total fuse length. */
    int p_length;

public:
    board(): p_width(0), p_height(0), p_length(0) {}

    /* Physical dimensions. */
    int width() const { return p_width; }
    int height() const { return p_height; }
    int area() const { return width() * height(); }
    /* Total fuse length. */
    int length() const { return p_length; }

    /* Returns the tile at (x, y). If x or y are negative, returns an OOB
     * tile. */
    tile get(int x, int y) const {
        if (x < 0 || y < 0)
            return {'*'};
        else if (x >= width() || y >= height())
            return {'\0'};
        else
            return p_data[y * width() + x];
    }
    /* Sets the tile at (x, y). x and y must be nonnegative and the tile at
     * (x, y) must be empty. */
    board& set(int x, int y, const tile& t) & {
        assert(x >= 0 && y >= 0);
        assert(!get(x, y));
        if (x >= width() || y >= height()) {
            int new_width = x >= width() ? x + 1 : width();
            int new_height = y >= height() ? y + 1 : height();
            vector<tile> temp(new_width * new_height, {'\0'});
            for (int l = 0; l < height(); ++l)
                copy(
                    p_data.begin() + l * width(),
                    p_data.begin() + (l + 1) * width(),
                    temp.begin() + l * new_width
                );
            p_data.swap(temp);
            p_width = new_width;
            p_height = new_height;
        }
        p_data[y * width() + x] = t;
        if (t.is_fuse())
            ++p_length;
        return *this;
    }
    board&& set(int x, int y, const tile& t) && { return move(set(x, y, t)); }

    /* Prints the board. */
    template <typename C, typename T>
    friend basic_ostream<C, T>& operator<<(basic_ostream<C, T>& os,
                                            const board& b) {
        for (int y = 0; y < b.height(); ++y) {
            for (int x = 0; x < b.width(); ++x)
                os << b.get(x, y);
            os << endl;
        }
        return os;
    }
};

/* A state of the tiling algorithm. */
struct state {
    /* The current board. */
    board b;
    /* The next firework to tile. */
    fireworks::const_iterator fw;
    /* The current location. */
    int x, y;
    /* The current movement direction. 'N'orth 'S'outh 'E'ast, 'W'est or
     * 'A'ny. */
    char dir;
};

/* Adds a state to the state-stack if its total fuse length and bounding-box
 * area are not worse than the current best ones. */
void add_state(vector<state>& states, int max_length, int max_area,
                state&& new_s) {
    if (new_s.b.length() < max_length ||
        (new_s.b.length() == max_length && new_s.b.area() <= max_area)
    )
        states.push_back(move(new_s));
}
/* Adds the state after moving in a given direction, if it's a valid move. */
void add_movement(vector<state>& states, int max_length, int max_area,
                    const state& s, char dir) {
    int x = s.x, y = s.y;
    char parallel_fuse;
    switch (dir) {
    case 'E': if (s.dir == 'W') return; ++x; parallel_fuse = '|'; break;
    case 'W': if (s.dir == 'E') return; --x; parallel_fuse = '|'; break;
    case 'S': if (s.dir == 'N') return; ++y; parallel_fuse = '-'; break;
    case 'N': if (s.dir == 'S') return; --y; parallel_fuse = '-'; break;
    }
    const tile t = s.b.get(s.x, s.y), nt = s.b.get(x, y);
    assert(t.is_fuse());
    if (nt.is_fuse() && !(t == parallel_fuse && nt == parallel_fuse))
        add_state(states, max_length, max_area, {s.b, s.fw, x, y, dir});
}
/* Adds the state after moving in a given direction and tiling a fuse, if it's a
 * valid move. */
void add_fuse(vector<state>& states, int max_length, int max_area,
                const state& s, char dir, char fuse) {
    int x = s.x, y = s.y;
    int sgn;
    bool horz;
    switch (dir) {
    case 'E': ++x; sgn = 1; horz = true; break;
    case 'W': --x; sgn = -1; horz = true; break;
    case 'S': ++y; sgn = 1; horz = false; break;
    case 'N': --y; sgn = -1; horz = false; break;
    }
    if (s.b.get(x, y))
        /* Tile is not empty. */
        return;
    /* Make sure we don't create cycles or reconnect a firework. */
    const tile t = s.b.get(s.x, s.y);
    assert(t.is_fuse());
    if (t == '-') {
        if (horz) {
            if (fuse == '-') {
                if (!s.b.get(x + sgn, y).is_vacant() ||
                    s.b.get(x, y - 1) == '|' ||
                    s.b.get(x, y + 1) == '|')
                    return;
            } else {
                if (s.b.get(x + sgn, y) == '-' ||
                    !s.b.get(x, y - 1).is_vacant() ||
                    !s.b.get(x, y + 1).is_vacant())
                    return;
            }
        } else {
            if (!s.b.get(x, y + sgn).is_vacant() ||
                s.b.get(x - 1, y) == '-' ||
                s.b.get(x + 1, y) == '-')
                return;
        }
    } else {
        if (!horz) {
            if (fuse == '|') {
                if (!s.b.get(x, y + sgn).is_vacant() ||
                    s.b.get(x - 1, y) == '-' ||
                    s.b.get(x + 1, y) == '-')
                    return;
            } else {
                if (s.b.get(x, y + sgn) == '|' ||
                    !s.b.get(x - 1, y).is_vacant() ||
                    !s.b.get(x + 1, y).is_vacant())
                    return;
            }
        } else {
            if (!s.b.get(x + sgn, y).is_vacant() ||
                s.b.get(x, y - 1) == '|' ||
                s.b.get(x, y + 1) == '|')
                return;
        }
    }
    /* Ok. */
    add_state(
        states,
        max_length,
        max_area,
        {board(s.b).set(x, y, {fuse, t.time + 1}), s.fw, x, y, dir}
    );
}
/* Adds the state after adding a firework at the given direction, if it's a
 * valid move. */
void add_firework(vector<state>& states, int max_length, int max_area,
                    const state& s, char dir) {
    int x = s.x, y = s.y;
    int sgn;
    bool horz;
    switch (dir) {
    case 'E': ++x; sgn = 1; horz = true; break;
    case 'W': --x; sgn = -1; horz = true; break;
    case 'S': ++y; sgn = 1; horz = false; break;
    case 'N': --y; sgn = -1; horz = false; break;
    }
    if (s.b.get(x, y))
        /* Tile is not empty. */
        return;
    /* Make sure we don't run into an undeliberate fuse. */
    if (horz) {
        if (s.b.get(x + sgn, y) == '-' || s.b.get(x, y - 1) == '|' ||
            s.b.get(x, y + 1) == '|')
            return;
    } else {
        if (s.b.get(x, y + sgn) == '|' || s.b.get(x - 1, y) == '-' ||
            s.b.get(x + 1, y) == '-')
            return;
    }
    /* Ok. */
    add_state(
        states,
        max_length,
        max_area,
        /* After adding a firework, we can move in any direction. */
        {board(s.b).set(x, y, {*s.fw}), s.fw + 1, s.x, s.y, 'A'}
    );
}
void add_possible_moves(vector<state>& states, int max_length, int max_area,
                        const state& s) {
    /* We add the new states in reverse-desirability order. The most
     * (aesthetically) desirable states are added last. */

    const tile t = s.b.get(s.x, s.y);
    assert(t.is_fuse());

    /* Move in all (possible) directions. */
    for (char dir : "WENS")
        if (dir) add_movement(states, max_length, max_area, s, dir);

    /* If the fuse is too short for the next firework, keep adding fuse. */
    if (t.time < s.fw->time) {
        if (t == '-') {
            add_fuse(states, max_length, max_area, s, 'N', '|');
            add_fuse(states, max_length, max_area, s, 'S', '|');
            add_fuse(states, max_length, max_area, s, 'W', '|');
            add_fuse(states, max_length, max_area, s, 'W', '-');
            add_fuse(states, max_length, max_area, s, 'E', '|');
            add_fuse(states, max_length, max_area, s, 'E', '-');
        } else {
            add_fuse(states, max_length, max_area, s, 'W', '-');
            add_fuse(states, max_length, max_area, s, 'E', '-');
            add_fuse(states, max_length, max_area, s, 'N', '-');
            add_fuse(states, max_length, max_area, s, 'N', '|');
            add_fuse(states, max_length, max_area, s, 'S', '-');
            add_fuse(states, max_length, max_area, s, 'S', '|');
        }
    } else if (t.time == s.fw->time) {
        /* If we have enough fuse for the next firework, place the firework (if
         * possible) and don't add more fuse, or else we'll never finish... */
        if (t == '-') {
            add_firework(states, max_length, max_area, s, 'W');
            add_firework(states, max_length, max_area, s, 'E');
        } else {
            add_firework(states, max_length, max_area, s, 'N');
            add_firework(states, max_length, max_area, s, 'S');
        }
    }
}

void thread_proc(mutex& lock, int& total_length, int& total_area,
                    int& failures) {
    fireworks fw;
    vector<state> states;

    while (true) {
        /* Read input. */
        string input;
        {
            lock_guard<mutex> lg(lock);

            while (!cin.eof() && input.empty())
                getline(cin, input);
            if (input.empty())
                break;
        }
        fw.clear();
        int length = 0, area;
        {
            stringstream is;
            is << input;
            while (!is.eof()) {
                char c;
                int t;
                if (is >> c >> t) {
                    /* Fireworks must be sorted by launch time. */
                    assert(fw.empty() || t >= fw.back().time);
                    fw.push_back({c, t});
                    length += t;
                }
            }
            assert(!fw.empty());
            area = fw.back().time * fw.back().time;
        }

        /* Add initial state. */
        states.push_back({board().set(0, 0, {'-', 1}), fw.begin(), 0, 0, 'A'});

        board solution;
        int moves = 0;
        int frustration_moves = FRUSTRATION_MOVES;

        while (!states.empty()) {
            /* Check for solutions (all fireworks consumed.) */
            while (!states.empty() && states.back().fw == fw.end()) {
                state& s = states.back();
                /* Did we find a better solution? */
                if (solution.area() == 0 || s.b.length() < length ||
                    (s.b.length() == length && s.b.area() < area)
                ) {
                    solution = move(s.b);
                    moves = 0;
                    length = solution.length();
                    area = solution.area();
                }
                states.pop_back();
            }

            /* Expand the top state. */
            if (!states.empty()) {
                state s = move(states.back());
                states.pop_back();
                add_possible_moves(states, length, area, s);
            }

            /* Getting frustrated? */
            ++moves;
            if (moves > frustration_moves) {
                /* Get rid of some data. */
                states.erase(
                    states.begin() + states.size() * FRUSTRATION_STATES_BACKOFF,
                    states.end()
                );
                frustration_moves *= FRUSTRATION_MOVES_BACKOFF;
                moves = 0;
            }
        }

        /* Print solution. */
        {
            lock_guard<mutex> lg(lock);

            cout << input << endl;

            if (solution.area())
                cout << solution;
            else {
                cout << "FAILED!" << endl;
                ++failures;
            }

            cout << "Length: " << length <<
                    ", Area: " << area <<
                    "." << endl << endl;
            total_length += length;
            total_area += area;
        }
    }
}

int main(int argc, const char* argv[]) {
    thread threads[THREAD_COUNT];
    mutex lock;
    int total_length = 0, total_area = 0, failures = 0;

    for (int i = 0; i < THREAD_COUNT; ++i)
        threads[i] = thread(thread_proc, ref(lock), ref(total_length),
                            ref(total_area), ref(failures));
    for (int i = 0; i < THREAD_COUNT; ++i)
        threads[i].join();

    cout << "Total Length: " << total_length <<
            ", Total Area: " << total_area <<
            ", Failures: " << failures <<
            "." << endl;
}

Pitone

Lunghezza totale: 17387, Area totale: 62285, Guasti: 44.


Uscita campione:

a 6 b 8 c 11 d 11 e 11 f 11 g 12 h 15 i 18 j 18 k 21 l 23 m 26 n 28 o 28 p 30 q 32 r 33 s 33 t 34
------a                
     |----f            
     |---c             
     b|||---h          
      |dg  |           
      e    |-j         
           |---k       
           i  |        
              |---m    
              l  |-o   
                 |--p  
                 n |--s
                   |-r 
                   q|  
                    t  
Length: 45, Area: 345.

Uscita completa: http://pastebin.com/raw.php?i=mgiqXCRK


Per riferimento, ecco un approccio molto più semplice. Cerca di collegare i fuochi d'artificio a un'unica linea di fusibili principale, creando una forma a "scala". Se un fuoco d'artificio non riesce a collegarsi direttamente alla linea principale (cosa che accade quando due o più fuochi d'artificio si accendono contemporaneamente) rintraccia la linea principale cercando un punto in cui può diramarsi perpendicolarmente verso il basso o verso destra (e fallisce se non esiste questo punto).

Non sorprende che faccia peggio del solutore della forza bruta, ma non con un margine enorme . Onestamente, mi aspettavo che la differenza fosse un po 'più grande.

Corri con: python fireworks.py.

from __future__ import print_function
import sys

total_length = total_area = failures = 0

for line in sys.stdin:
    # Read input.
    line = line.strip()
    if line == "": continue
    fws = line.split(' ')
    # The fireworks are a list of pairs of the form (<letter>, <time>).
    fws = [(fws[i], int(fws[i + 1])) for i in xrange(0, len(fws), 2)]

    # The board is a dictionary of the form <coord>: <tile>.
    # The first tile marks the "starting point" and is out-of-bounds.
    board = {(-1, 0): '*'}
    # The tip of the main "staircase" fuse.
    tip_x, tip_y = -1, 0
    tip_time = 0
    # We didn't fail. Yet...
    failed = False

    for (fw, fw_time) in fws:
        dt = fw_time - tip_time
        # Can we add the firework to the main fuse line?
        if dt > 0:
            # We can. Alternate the direction to create a "staircase" pattern.
            if board[(tip_x, tip_y)] == '-':    dx, dy = 0, 1; fuse = '|'
            else:                               dx, dy = 1, 0; fuse = '-'
            x, y = tip_x, tip_y
            tip_x += dt * dx
            tip_y += dt * dy
            tip_time += dt
        else:
            # We can't. Trace the main fuse back until we find a point where we
            # can thread, or fail if we reach the starting point.
            x, y = tip_x, tip_y
            while board[(x, y)] != '*':
                horz = board[(x, y)] == '-'
                if horz:    dx, dy = 0, 1; fuse = '|'
                else:       dx, dy = 1, 0; fuse = '-'
                if dt > 0 and (x + dx, y + dy) not in board: break
                if horz:    x -= 1
                else:       y -= 1
                dt += 1
            if board[(x, y)] == '*':
                failed = True
                break
        # Add the fuse and firework.
        for i in xrange(dt):
            x += dx; y += dy
            board[(x, y)] = fuse
        board[(x + dx, y + dy)] = fw

    # Print output.
    print(line)
    if not failed:
        max_x, max_y = (max(board, key=lambda p: p[i])[i] + 1 for i in (0, 1))
        for y in xrange(max_y):
            for x in xrange(max_x):
                print(board.get((x, y), ' '), end = "")
            print()
        length = len(board) - len(fws) - 1
        area = max_x * max_y
    else:
        print("FAILED!")
        failures += 1
        length = sum(map(lambda fw: fw[1], fws))
        area = fws[-1][1] ** 2
    print("Length: %d, Area: %d.\n" % (length, area))
    total_length += length; total_area += area

print("Total Length: %d, Total Area: %d, Failures: %d." %
        (total_length, total_area, failures))

Per curiosità, quanto tempo ci vuole per completare i parametri attuali?
Geobits il

@Geobits: dipende dalla macchina, ovviamente, e non ho guardato troppo da vicino, ma penso a venti minuti, dare o prendere.
DarwinBot,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.