#pragma GCC target("avx2") #pragma GCC optimize("O3") #pragma GCC optimize("unroll-loops") #include using namespace std; using lint = long long int; using P = pair; using PL = pair; #define FOR(i, begin, end) for(int i=(begin),i##_end_=(end);i=i##_begin_;i--) #define REP(i, n) FOR(i,0,n) #define IREP(i, n) IFOR(i,0,n) #define ALL(a) (a).begin(),(a).end() constexpr int MOD = 1000000007; constexpr lint B1 = 1532834020; constexpr lint M1 = 2147482409; constexpr lint B2 = 1388622299; constexpr lint M2 = 2147478017; constexpr int INF = 2147483647; void yes(bool expr) {cout << (expr ? "Yes" : "No") << "\n";} templatevoid chmax(T &a, const T &b) { if (avoid chmin(T &a, const T &b) { if (b dx = {0, 0, -1, 1}; vector dy = {-1, 1, 0, 0}; vector dc = {'L', 'R', 'U', 'D'}; vector dx9 = {0, 0, -1, -1, -1, 0, 1, 1, 1}; vector dy9 = {0, -1, -1, 0, 1, 1, 1, 0, -1}; vector> move_idx = { {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 8, 7, 6, 5, 4, 3, 2}, {0, 3, 4, 5, 6, 7, 8, 1, 2}, {0, 3, 2, 1, 8, 7, 6, 5, 4}, {0, 5, 6, 7, 8, 1, 2, 3, 4}, {0, 5, 4, 3, 2, 1, 8, 7, 6}, {0, 7, 8, 1, 2, 3, 4, 5, 6}, {0, 7, 6, 5, 4, 3, 2, 1, 8}, }; vector> move_dir = { {0, 2, 1, 1, 3, 3, 0, 0}, {0, 3, 1, 1, 2, 2, 0, 0}, {2, 1, 3, 3, 0, 0, 2, 2}, {2, 0, 3, 3, 1, 1, 2, 2}, {1, 3, 0, 0, 2, 2, 1, 1}, {1, 2, 0, 0, 3, 3, 1, 1}, {3, 0, 2, 2, 1, 1, 3, 3}, {3, 1, 2, 2, 0, 0, 3, 3}, }; int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int N, M; cin >> N >> M; vector A(N); REP(i, N) cin >> A[i]; vector C(M), L(M); vector> a(M), b(M); REP(i, M) { cin >> C[i] >> L[i]; REP(j, L[i]) { int x, y; cin >> x >> y; a[i].push_back(x); b[i].push_back(y); } } vector shopx, shopy; REP(i, N) REP(j, N) { if(A[i][j] == '@') { shopx.push_back(i); shopy.push_back(j); } } int K = shopx.size(); mt19937 mt(0); //爆弾屋の選択 //要件1. 2つの爆弾屋で爆破領域が被ってはいけない //要件2. 全ての爆弾屋の爆破領域の和集合が全てのマスに到達 //評価関数:各爆弾屋から最も近いマスをその爆弾屋の領域として、爆破しやすさの和を計算することで決めたい //爆弾屋の個数は4つが良いが5つのケースも考える? double best_value = 0; vector best_shop; vector> best_shop_area; REP(z, 5000) { //爆弾屋の個数 int shopcnt = 4; set shopset; REP(zz, 1000) { if (shopset.size() == shopcnt) break; int r = mt() % K; bool ok = true; if(shopx[r] == 0 || shopx[r] == N-1 || shopy[r] == 0 || shopy[r] == N-1) ok = false; for(int shopid : shopset) { //要件1のチェック int x1 = shopx[shopid], y1 = shopy[shopid]; int x2 = shopx[r], y2 = shopy[r]; //移動分も考慮して余裕を持たせる if(abs(x1 - x2) <= 21 && abs(y1 - y2) <= 21) ok = false; //マップ端の爆弾屋は使用しない } if(!ok) continue; shopset.insert(r); } if(shopset.size() != shopcnt) continue; vector shop(shopset.begin(), shopset.end()); bool ok = true; //要件2のチェック vector> can_bomb(N, vector(N, false)); REP(i, shop.size()) { int x = shopx[shop[i]], y = shopy[shop[i]]; FOR(dx, -20, 21) FOR(dy, -20, 21) { int nx = x + dx, ny = y + dy; if(nx < 0 || nx >= N || ny < 0 || ny >= N) continue; can_bomb[nx][ny] = true; } } int cnt = 0; REP(i, N) REP(j, N) if(can_bomb[i][j]) cnt++; if(cnt != N*N) ok = false; if(!ok) continue; //爆弾屋領域の決定と評価値の計算(最も近い爆弾屋を割り当てる) double value = 0; vector> shop_area(N, vector(N, -1)); REP(i, N) REP(j, N) { int min_dist = INF; REP(k, shop.size()) { int x = shopx[shop[k]], y = shopy[shop[k]]; int dist = max(abs(x - i), abs(y - j)); if(dist < min_dist) { min_dist = dist; shop_area[i][j] = shop[k]; } } assert(min_dist <= 20); //評価値 value += 1.0 / (min_dist * 0.3 + 1); } if(value > best_value) { best_value = value; best_shop = shop; best_shop_area = shop_area; } } //爆破計画の決定 //今いるマスから最も近い爆弾屋に移動 //その爆弾屋の爆破領域にある建物を全て破壊できるような爆破計画を決める //爆弾屋のマスと周囲1マスの計9マスにおける爆弾の集合を焼く //爆弾は最初に必ず全て買う必要がある(爆弾屋はすぐに吹っ飛ぶので) //周囲1マスの移動経路は8通りあるので最もコストの低い方法を使う vector

ans; int ans_cost = 0; int x = 0; int y = 0; set rest_shop(best_shop.begin(), best_shop.end()); while(!rest_shop.empty()) { int min_dist = INF; int min_shop = -1; for(int shopid : rest_shop) { int dist = abs(shopx[shopid] - x) + abs(shopy[shopid] - y); if(dist < min_dist) { min_dist = dist; min_shop = shopid; } } rest_shop.erase(min_shop); //移動 //TODO: ちゃんと建物のコストを考慮してダイクストラ? bool can_cost_2 = false; while(x != shopx[min_shop] || y != shopy[min_shop]) { bool moved = false; REP(dir, 4) { int nx = x + dx[dir]; int ny = y + dy[dir]; if(nx < 0 || nx >= N || ny < 0 || ny >= N) continue; if(!can_cost_2 && A[nx][ny] != '.') continue; if(abs(x - shopx[min_shop]) + abs(y - shopy[min_shop]) > abs(nx - shopx[min_shop]) + abs(ny - shopy[min_shop])) { ans.push_back({1, dir}); x = nx; y = ny; if(A[x][y] == '.') ans_cost += 1; else ans_cost += 2; can_cost_2 = false; moved = true; } } if(!moved) can_cost_2 = true; } //爆破計画を焼く int best_cost = 1e9; int best_move_id = -1; vector best_bomb_set(9); vector> best_bomb_cnt(N, vector(N, 0)); int best_cost_sub = 1e9; vector best_bomb_set_sub(9); vector> best_bomb_cnt_sub(N, vector(N, 0)); int rest = 0; REP(i, N) REP(j, N) if(A[i][j] != '.' && best_shop_area[i][j] == min_shop) rest++; //計画済みの爆弾をいくつか消す→全ての建物を破壊できるようになるまでランダムに爆弾を追加 を繰り返す auto start_time = chrono::system_clock::now(); double start_temp = 2.2; double end_temp = 1.2; double temp = start_temp; int loop = 0; while(true) { if(loop%500 == 0) { double time = chrono::duration_cast(chrono::system_clock::now() - start_time).count(); cerr << loop << " " << best_cost_sub << " " << best_cost << " " << time << "\n"; if(time > TIME_LIMIT) break; temp = start_temp + (end_temp - start_temp) * time / TIME_LIMIT; } loop++; vector bomb_set(best_bomb_set_sub); vector> bomb_cnt(best_bomb_cnt_sub); //ランダムに爆弾を消す if(loop > 1) { int remove_cnt = 1; REP(z, remove_cnt) { while(true) { int r = mt() % 9; int s = mt() % M; if(bomb_set[r] >> s & 1) { bomb_set[r] ^= 1 << s; REP(i, L[s]) { int nx = x + a[s][i] + dx9[r]; int ny = y + b[s][i] + dy9[r]; if(nx < 0 || nx >= N || ny < 0 || ny >= N) continue; bomb_cnt[nx][ny]--; if(A[nx][ny] != '.' && best_shop_area[nx][ny] == min_shop && bomb_cnt[nx][ny] == 0) rest++; } break; } } } } //restが0になるまで爆弾を追加する //restが減らないような追加は許さない while(rest > 0) { int r = mt() % 9; int s = mt() % M; if(bomb_set[r] >> s & 1) continue; bool ok = false; REP(i, L[s]) { int nx = x + a[s][i] + dx9[r]; int ny = y + b[s][i] + dy9[r]; if(nx < 0 || nx >= N || ny < 0 || ny >= N) continue; if(A[nx][ny] != '.' && best_shop_area[nx][ny] == min_shop && bomb_cnt[nx][ny] == 0) { ok = true; break; } } if(!ok) continue; bomb_set[r] ^= 1 << s; REP(i, L[s]) { int nx = x + a[s][i] + dx9[r]; int ny = y + b[s][i] + dy9[r]; if(nx < 0 || nx >= N || ny < 0 || ny >= N) continue; if(A[nx][ny] != '.' && best_shop_area[nx][ny] == min_shop && bomb_cnt[nx][ny] == 0) rest--; bomb_cnt[nx][ny]++; } } //コストの計算(8種類の移動方法に関して計算し最も小さい値) int cost = 1e9; int move_id = -1; REP(move_id_sub, 8) { int cost_sub = 0; int have_bombs = 0; REP(i, 9) REP(j, M) if(bomb_set[i] >> j & 1) have_bombs++; for(int i : move_idx[move_id_sub]) { if(have_bombs == 0) break; REP(j, M) if(bomb_set[i] >> j & 1) { cost_sub += C[j]; have_bombs--; } cost_sub += 1 * (have_bombs + 1) * (have_bombs + 1); } if(cost_sub < cost) { cost = cost_sub; move_id = move_id_sub; } } double prob = exp(((double)best_cost_sub - cost)*pow(0.1, temp)); if(cost <= best_cost_sub || prob > (mt()%INF)/(double)INF) { best_cost_sub = cost; best_bomb_set_sub = bomb_set; best_bomb_cnt_sub = bomb_cnt; if(cost < best_cost) { best_cost = cost; best_bomb_set = bomb_set; best_bomb_cnt = bomb_cnt; best_move_id = move_id; } } } //爆弾を購入 int have_bombs = 0; REP(i, 9) REP(j, M) if(best_bomb_set[i] >> j & 1) { assert(A[x][y] == '@'); ans.push_back({2, j+1}); ans_cost += C[j]; have_bombs++; } //爆破計画に従って行動する //とりあえず単一の移動経路だけ考える REP(k, 9) { int i = move_idx[best_move_id][k]; REP(j, M) if(best_bomb_set[i] >> j & 1) { ans.push_back({3, j+1}); have_bombs--; REP(k, L[j]) { int nx = x + a[j][k]; int ny = y + b[j][k]; if(nx < 0 || nx >= N || ny < 0 || ny >= N) continue; if(A[nx][ny] != '.') A[nx][ny] = '.'; } } if(have_bombs == 0) break; int dir = move_dir[best_move_id][k]; ans.push_back({1, dir}); x += dx[dir]; y += dy[dir]; if(A[x][y] == '.') ans_cost += 1 * (have_bombs + 1) * (have_bombs + 1); else ans_cost += 2 * (have_bombs + 1) * (have_bombs + 1); } REP(i, N) cerr << A[i] << "\n"; } //爆破計画の出力 cout << ans.size() << "\n"; for(P p : ans) { if(p.first != 1) cout << p.first << " " << p.second << "\n"; else cout << p.first << " " << dc[p.second] << "\n"; } cerr << ans_cost << "\n"; }