#include // clang-format off using namespace std; using ll=long long; using ull=unsigned long long; using pll=pair; const ll INF=4e18; void print0(){}; template void print0(H h,T... t){cout<void print(H h,T... t){print0(h);if(sizeof...(T)>0)print0(" ");print(t...);} #define debug1(a) { cerr<<#a<<":"< T random_choice(vector &vec) { return vec[engine() % vec.size()]; } bool anneal_accept(double new_score, double old_score, double cur_time, double begin_time, double end_time, double begin_temp, double end_temp) { const int ANNEAL_RND = 1e8; const double ANNEAL_EPS = 1e-6; double temp = (begin_temp * (end_time - cur_time) + end_temp * (cur_time - begin_time)) / (end_time - begin_time); return (exp((new_score - old_score) / temp) > double(engine() % ANNEAL_RND) / ANNEAL_RND + ANNEAL_EPS); } } // namespace marathon struct operation_t { int kind; int xyz; }; struct bomb_t { int cost; vector ranges; }; const int _U_ = 0; const int _R_ = 1; const int _D_ = 2; const int _L_ = 3; const int N = 50; const int M = 20; vector mvs = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; char TBL_INIT[N][N]; bomb_t BOMBS[M]; int getmove(point src, point dest) { assert(abs(src.r - dest.r) + abs(src.c - dest.c) == 1); if (src.r < dest.r) { return _D_; } if (src.r > dest.r) { return _U_; } if (src.c < dest.c) { return _R_; } if (src.c > dest.c) { return _L_; } } bool ingrid(int i, int j) { return 0 <= i && i < N && 0 <= j && j < N; } bool ingrid(point p) { return ingrid(p.r, p.c); } struct anneal_state_t { vector> bombs; double calc_score(vector> &break_score, vector &breaks, point now_shop) { static int hit[N][N]; for (auto br : breaks) { hit[br.r][br.c] = 0; } // TODO 差分計算 // TODO かなり嘘な計算ではある const double bomb_num_ratio = 1.1; double score = 0; score -= bomb_num_ratio * bombs.size(); for (auto b : bombs) { int bombid = b.first; point center = b.second; for (auto nxt : BOMBS[bombid].ranges) { point pt = center + nxt; if (!ingrid(pt)) continue; hit[pt.r][pt.c]++; if (hit[pt.r][pt.c] == 1) { score += 1.0 * break_score[pt.r][pt.c]; } } } if (hit[now_shop.r][now_shop.c] >= 2) { score -= 10000; } return score; } }; vector> anneal_bomb_puts(vector> &tbl, vector> &break_score, point now_shop) { vector breaks; int init_shopcnt = 0; for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { point pt = {r, c}; if (tbl[r][c] != '.') { breaks.push_back({r, c}); } if (TBL_INIT[r][c] == '@') init_shopcnt++; } } const double END_TIME = 2900; double begin_time = marathon::now(); double end_time = begin_time + 0.95 * END_TIME / init_shopcnt; anneal_state_t state; state.bombs.clear(); double old_score = state.calc_score(break_score, breaks, now_shop); while (marathon::now() < end_time) { // 10*10マスの中に爆弾を置く // TODO 爆弾をなくす遷移(破壊再構築寄り) int r = now_shop.r + marathon::randint(-3, 3); int c = now_shop.c + marathon::randint(-3, 3); if (!ingrid(r, c)) continue; int b = marathon::engine() % M; state.bombs.push_back({b, {r, c}}); double new_score = state.calc_score(break_score, breaks, now_shop); if (new_score > old_score) { old_score = new_score; } else { state.bombs.pop_back(); } } return state.bombs; } void solve1(point &now, vector> &tbl, vector &ops) { assert(tbl[now.r][now.c] == '@'); // 必ず破壊すべきtargetと、できれば破壊したいtarget const int DISTANCE_LIM = 10; // TODO vector> break_score(N, vector(N)); { vector walls; for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { point pt = {r, c}; if (tbl[r][c] == '#') { walls.push_back({r, c}); } } } vector shops; for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { if (tbl[r][c] == '@') { point shop = {r, c}; shops.push_back(shop); if (shop != now) { if (walls.size() == 0) { int distance = max(abs(shop.r - now.r), abs(shop.c - now.c)); // TODO if (distance <= DISTANCE_LIM) { break_score[shop.r][shop.c] += 2; // なるべく壊しておきたい } else { break_score[shop.r][shop.c] += 0.01; // 壊せるとちょっとうれしい } } else { break_score[r][c] += -10000; } } } } } break_score[now.r][now.c] += 2; for (auto wall : walls) { { int distance = max(abs(wall.r - now.r), abs(wall.c - now.c)); // TODO if (distance <= DISTANCE_LIM) { break_score[wall.r][wall.c] += 2; // なるべく壊しておきたい } else { break_score[wall.r][wall.c] += 0.01; // 壊せるとちょっとうれしい } } int nearest_distance = inf; point nearest_shop = {-1, -1}; for (auto shop : shops) { const int DISTANCE_LIM = 10; // TODO int distance = max(abs(wall.r - shop.r), abs(wall.c - shop.c)); // TODO if (nearest_distance > distance) { nearest_distance = distance; nearest_shop = shop; } } if (nearest_shop == now) { break_score[wall.r][wall.c] += 100; // ほぼ確実に壊しておきたい } } } auto bomb_puts = anneal_bomb_puts(tbl, break_score, now); // とりあえず貪欲(TODO 焼きなます) { for (auto b : bomb_puts) { int bombid = b.first; ops.push_back({2, bombid}); } pair last_bomb_put = {-1, {-1, -1}}; // shopを壊すのは最後にする for (auto b : bomb_puts) { int bombid = b.first; point center = b.second; for (auto nxt : BOMBS[bombid].ranges) { point pt = center + nxt; if (pt == now) { last_bomb_put = b; } } } debug1(marathon::now()); while (bomb_puts.size()) { priority_queue, vector>, greater>> pq; pq.push({0, now, {-1, -1}}); vector> costs(N, vector(N, inf)); vector> froms(N, vector(N, {-1, -1})); vector> destination(N, vector(N)); for (auto b : bomb_puts) { point p = b.second; if (b == last_bomb_put && bomb_puts.size() >= 2) continue; destination[p.r][p.c] = true; } pair tgt_bomb_put = {-1, {-1, -1}}; while (pq.size()) { int cost; point u, frm; tie(cost, u, frm) = pq.top(); pq.pop(); if (costs[u.r][u.c] <= cost) continue; costs[u.r][u.c] = cost; froms[u.r][u.c] = frm; if (destination[u.r][u.c]) { for (auto b : bomb_puts) { if (b == last_bomb_put && bomb_puts.size() >= 2) continue; if (b.second != u) continue; tgt_bomb_put = b; break; } break; } for (auto mv : mvs) { point v = mv + u; if (!ingrid(v)) continue; int nc = cost + 1; if (tbl[v.r][v.c] != '.') { nc = cost + 2; } pq.push({nc, v, u}); } } assert(tgt_bomb_put.first >= 0); { point cur = tgt_bomb_put.second; vector route; while (true) { route.push_back(cur); if (cur == now) break; cur = froms[cur.r][cur.c]; } reverse(route.begin(), route.end()); for (int i = 1; i < int(route.size()); i++) { point pre = route[i - 1]; point nxt = route[i]; int mvid = getmove(pre, nxt); ops.push_back({1, mvid}); } now = tgt_bomb_put.second; } { ops.push_back({3, tgt_bomb_put.first}); int bombid = tgt_bomb_put.first; for (auto nxt : BOMBS[bombid].ranges) { point pt = now + nxt; if (!ingrid(pt)) continue; tbl[pt.r][pt.c] = '.'; } } bomb_puts.erase(std::find(bomb_puts.begin(), bomb_puts.end(), tgt_bomb_put)); } debug1(marathon::now()); } } void solve() { vector ops; // point last_bomb_shop = {-1, -1}; // 念のため最後の爆弾屋は中心近くにする { int min_centerness = inf; for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { if (TBL_INIT[r][c] == '@') { int centerness = abs(r - N / 2) + abs(c - N / 2); if (min_centerness > centerness) { // last_bomb_shop = {r, c}; min_centerness = centerness; } } } } } // point now = {0, 0}; vector> tbl(N, vector(N)); for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { tbl[r][c] = TBL_INIT[r][c]; } } while (true) { // 一番近くにあるshopに移動 //TODO TSP vector shops; for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { if (tbl[r][c] == '@') shops.push_back({r, c}); } } debug2(marathon::now(), shops.size()); if (shops.size() == 0) break; point mvto_shop = {-1, -1}; { int maxscore = -1; for (auto shop : shops) { // if (shop == last_bomb_shop && shops.size() >= 2) continue; int score = 0; for (int r = shop.r - 10; r <= shop.r + 10; r++) { for (int c = shop.c - 10; c <= shop.c + 10; c++) { if (!ingrid(r, c)) continue; if (tbl[r][c] == '#') score++; } } if (maxscore < score) { maxscore = score; mvto_shop = shop; } } // queue que; // vector> done(N, vector(N)); // que.push(now); // done[now.r][now.c] = true; // while (que.size()) { // point u = que.front(); // que.pop(); // if (tbl[u.r][u.c] == '@') { // if (u == last_bomb_shop && totalshop >= 2) continue; // 最後のショップに行くのは残りショップ数が1のときだけ // mvto_shop = u; // break; // } // for (auto mv : mvs) { // point v = u + mv; // if (!ingrid(v)) continue; // if (done[v.r][v.c]) continue; // done[v.r][v.c] = true; // que.push(v); // } // } } // shopに移動 { priority_queue, vector>, greater>> pq; pq.push({0, now, {-1, -1}}); vector> costs(N, vector(N, inf)); vector> froms(N, vector(N, {-1, -1})); while (pq.size()) { int cost; point u, frm; tie(cost, u, frm) = pq.top(); pq.pop(); if (costs[u.r][u.c] <= cost) continue; costs[u.r][u.c] = cost; froms[u.r][u.c] = frm; if (u == mvto_shop) break; for (auto mv : mvs) { point v = mv + u; if (!ingrid(v)) continue; int nc = cost + 1; if (tbl[v.r][v.c] != '.') { nc = cost + 2; } pq.push({nc, v, u}); } } { point cur = mvto_shop; vector route; while (true) { route.push_back(cur); if (cur == now) break; cur = froms[cur.r][cur.c]; } reverse(route.begin(), route.end()); for (int i = 1; i < int(route.size()); i++) { point pre = route[i - 1]; point nxt = route[i]; int mvid = getmove(pre, nxt); ops.push_back({1, mvid}); } now = mvto_shop; } } solve1(now, tbl, ops); } cout << ops.size() << endl; for (auto op : ops) { if (op.kind == 1) { char d = '.'; if (op.xyz == _L_) d = 'L'; if (op.xyz == _R_) d = 'R'; if (op.xyz == _U_) d = 'U'; if (op.xyz == _D_) d = 'D'; cout << op.kind << " " << d << endl; } else { cout << op.kind << " " << op.xyz + 1 << endl; } } } int main() { marathon::marathon_init(); int n, m; cin >> n >> m; for (int r = 0; r < N; r++) { for (int c = 0; c < N; c++) { cin >> TBL_INIT[r][c]; } } for (int b = 0; b < M; b++) { int c, l; cin >> c >> l; bomb_t bomb; bomb.cost = c; for (int i = 0; i < l; i++) { int dr, dc; cin >> dr >> dc; bomb.ranges.push_back({dr, dc}); } BOMBS[b] = bomb; } solve(); }