融合烟花


13

总览

在给出烟花a-z和时间清单的情况下3-78,为它们安排保险丝,使它们在正确的时间点亮。

输入行以空格分隔的字母和数字给出:

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

这个例子表明,烟花a需要光在时间3bc都在6d8,与ef都在9。每条线对应一张地图。

输出是每行的保险丝/烟花图,使用符号|-显示保险丝,使用字母显示烟花。

-保险丝所连接到保险丝和焰火直接左/右它,而|具有低于上述那些/熔丝所连接。例如,熔丝||连接的,并且-|

例如,以上两种可能的答案是:

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

所有保险丝图应从-左上角的一个开始。那就是您点亮保险丝的地方。保险丝的每个字符耗时一秒钟。如您所见,a在两个图表中,三秒内都达到了,b在六个图中等等。

现在,以上给出的两个映射对于给定的输入均有效,但显然更有效。左边的仅使用13个保险丝,而右边的仅使用20个保险丝。

保险丝不会通过烟花燃烧!因此,对于input a 3 b 5,这是无效的:

---a--b

挑战

您的目标是在所有测试用例中尽量减少使用的保险丝数量。计分非常简单,使用的保险丝总数为单位。

如果您无法为测试案例生成地图,则无论该案例是否是不可能的案例,该案例的得分都是所有时间的总和(上例中为41)。

如果是平局,则修改得分,以便赢得最紧凑的地图。抢七得分是每张地图边框的面积。即,最长线的长度乘以线数。对于“不可能”地图,这是数量最大的正方形(上例中为81)。

如果将提交与这两种评分方法联系在一起,则联系将进入较早的条目/编辑。

为了进行验证,您的程序必须是确定性的。

测试用例

有250测试用例,设在这里。每个都有4到26个烟花。最小熔断时间的烟花是3.在每种情况下的烟花“分类”的时间和字母,意为b永不光 a

发布时,请包括您的完整程序,您的总分以及(至少)文件中给出的第一个测试用例的结果图:

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 

能否同时放出许多烟花?
IngoBürk'14

基本上是。我没有在测试用例中寻找最大的实例,但是我知道至少有四个。两根保险丝之间的时间是rand.nextInt(5)%4,所以有40%的机会0,每根20%的机会1,2,3
Geobits,2014年

只是一个建议:我会在保险丝的连接或改变方向上使用“ +”号,这将使输出图形恕我直言更加直观!
瑕疵的

@flawr我会允许的,只要它以不改变分数的方式完成即可。例如,-+-代替---不会自动将烟花连接到上方/下方的位置,仍然必须在|上方/下方将其连接至烟花。-+-在地方-|-是好的,因为它是。
Geobits,2014年

所有测试用例都可以解决吗?例如,如果在第3点有五支或多支烟花要燃放,我认为您不可能一开始就把它们都放得足够近。同样,您也许可以容纳所有这些物品,但它们可能会阻塞通往室外的道路,以备日后使用。
2014年

Answers:


3

C ++

总长度:9059,总面积:27469,故障:13。

注意:分数包括失败处罚。


样本输出:

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.

完整输出: http : //pastebin.com/raw.php?i=spBUidBV


您不只是喜欢暴力解决方案吗?这不仅仅是一个简单的回溯算法:我们的不倦的工人在地图上移动,根据需要放置保险丝和烟花,同时在任何点测试所有可能的移动。好吧,几乎-我们确实限制了动作的集合,并尽早放弃了非最佳状态,这样它就不会花费太长的时间(尤其是使其终止)。要特别注意不要创建任何周期或意外周期路径,并且不要以与我们返回时相同的方式返回,因此可以确保我们不会两次访问同一州。即使这样,找到最佳解决方案仍需要一段时间,因此如果花费太长时间,我们最终会放弃优化解决方案。

该算法仍有一定的余量。一方面,通过增加FRUSTRATION参数可以找到更好的解决方案。没有竞争性的自动提款机,但是在以下情况下可以提高这些数字:

编译为:g++ fireworks.cpp -ofireworks -std=c++11 -pthread -O3

运行:./fireworks

从STDIN读取输入,并将输出写入STDOUT(可能是乱序)。

/* 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;
}

蟒蛇

总长度:17387,总面积:62285,失败:44。


样本输出:

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.

完整输出: http : //pastebin.com/raw.php?i=mgiqXCRK


供参考,这是一种更简单的方法。它试图将烟花连接到一条主保险丝线上,从而形成“楼梯”形状。如果烟火无法直接连接到主线(当两个或多个烟火同时发光时发生),它将追溯到主线,寻找可以垂直向下或向右分支的点(如果不存在这一点。)

不出所料,它确实比蛮力解算差,但不是一个巨大的保证金。老实说,我希望两者之间的差异会更大。

运行: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))

出于好奇,使用当前参数需要多长时间?
Geobits 2014年

@Geobits:很明显,这是取决于机器的,我并没有看得太近,但是我想大概要花20分钟。
DarwinBot 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.