結果
問題 | No.5019 Hakai Project |
ユーザー | Shun_PI |
提出日時 | 2023-11-19 01:06:54 |
言語 | C++14 (gcc 12.3.0 + boost 1.83.0) |
結果 |
AC
|
実行時間 | 2,710 ms / 3,000 ms |
コード長 | 10,366 bytes |
コンパイル時間 | 4,490 ms |
コンパイル使用メモリ | 231,444 KB |
実行使用メモリ | 6,676 KB |
スコア | 2,112,470,654 |
最終ジャッジ日時 | 2023-11-19 01:09:20 |
合計ジャッジ時間 | 145,637 ms |
ジャッジサーバーID (参考情報) |
judge11 / judge15 |
純コード判定しない問題か言語 |
(要ログイン)
テストケース
テストケース表示入力 | 結果 | 実行時間 実行使用メモリ |
---|---|---|
testcase_00 | AC | 2,607 ms
6,676 KB |
testcase_01 | AC | 2,595 ms
6,676 KB |
testcase_02 | AC | 2,645 ms
6,676 KB |
testcase_03 | AC | 2,652 ms
6,676 KB |
testcase_04 | AC | 2,621 ms
6,676 KB |
testcase_05 | AC | 2,648 ms
6,676 KB |
testcase_06 | AC | 2,659 ms
6,676 KB |
testcase_07 | AC | 2,661 ms
6,676 KB |
testcase_08 | AC | 2,635 ms
6,676 KB |
testcase_09 | AC | 2,613 ms
6,676 KB |
testcase_10 | AC | 2,695 ms
6,676 KB |
testcase_11 | AC | 2,635 ms
6,676 KB |
testcase_12 | AC | 2,666 ms
6,676 KB |
testcase_13 | AC | 2,661 ms
6,676 KB |
testcase_14 | AC | 2,639 ms
6,676 KB |
testcase_15 | AC | 2,623 ms
6,676 KB |
testcase_16 | AC | 2,707 ms
6,676 KB |
testcase_17 | AC | 2,663 ms
6,676 KB |
testcase_18 | AC | 2,603 ms
6,676 KB |
testcase_19 | AC | 2,621 ms
6,676 KB |
testcase_20 | AC | 2,632 ms
6,676 KB |
testcase_21 | AC | 2,635 ms
6,676 KB |
testcase_22 | AC | 2,652 ms
6,676 KB |
testcase_23 | AC | 2,659 ms
6,676 KB |
testcase_24 | AC | 2,621 ms
6,676 KB |
testcase_25 | AC | 2,667 ms
6,676 KB |
testcase_26 | AC | 2,649 ms
6,676 KB |
testcase_27 | AC | 2,666 ms
6,676 KB |
testcase_28 | AC | 2,671 ms
6,676 KB |
testcase_29 | AC | 2,617 ms
6,676 KB |
testcase_30 | AC | 2,682 ms
6,676 KB |
testcase_31 | AC | 2,659 ms
6,676 KB |
testcase_32 | AC | 2,658 ms
6,676 KB |
testcase_33 | AC | 2,658 ms
6,676 KB |
testcase_34 | AC | 2,589 ms
6,676 KB |
testcase_35 | AC | 2,639 ms
6,676 KB |
testcase_36 | AC | 2,598 ms
6,676 KB |
testcase_37 | AC | 2,662 ms
6,676 KB |
testcase_38 | AC | 2,619 ms
6,676 KB |
testcase_39 | AC | 2,645 ms
6,676 KB |
testcase_40 | AC | 2,640 ms
6,676 KB |
testcase_41 | AC | 2,586 ms
6,676 KB |
testcase_42 | AC | 2,686 ms
6,676 KB |
testcase_43 | AC | 2,677 ms
6,676 KB |
testcase_44 | AC | 2,618 ms
6,676 KB |
testcase_45 | AC | 2,664 ms
6,676 KB |
testcase_46 | AC | 2,634 ms
6,676 KB |
testcase_47 | AC | 2,612 ms
6,676 KB |
testcase_48 | AC | 2,710 ms
6,676 KB |
testcase_49 | AC | 2,629 ms
6,676 KB |
ソースコード
#pragma GCC target("avx2") #pragma GCC optimize("O3") #pragma GCC optimize("unroll-loops") #include <bits/stdc++.h> using namespace std; using lint = long long int; using P = pair<int, int>; using PL = pair<lint, lint>; #define FOR(i, begin, end) for(int i=(begin),i##_end_=(end);i<i##_end_;i++) #define IFOR(i, begin, end) for(int i=(end)-1,i##_begin_=(begin);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";} template<class T>void chmax(T &a, const T &b) { if (a<b) a=b; } template<class T>void chmin(T &a, const T &b) { if (b<a) a=b; } constexpr int TIME_LIMIT = 600; vector<int> dx = {0, 0, -1, 1}; vector<int> dy = {-1, 1, 0, 0}; vector<char> dc = {'L', 'R', 'U', 'D'}; vector<int> dx9 = {0, 0, -1, -1, -1, 0, 1, 1, 1}; vector<int> dy9 = {0, -1, -1, 0, 1, 1, 1, 0, -1}; int main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int N, M; cin >> N >> M; vector<string> A(N); REP(i, N) cin >> A[i]; vector<int> C(M), L(M); vector<vector<int>> 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<int> 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<int> best_shop; vector<vector<int>> best_shop_area; REP(z, 5000) { //爆弾屋の個数 int shopcnt = 4; set<int> 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<int> shop(shopset.begin(), shopset.end()); bool ok = true; //要件2のチェック vector<vector<bool>> can_bomb(N, vector<bool>(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<vector<int>> shop_area(N, vector<int>(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.2 + 1); } if(value > best_value) { best_value = value; best_shop = shop; best_shop_area = shop_area; } } //爆破計画の決定 //今いるマスから最も近い爆弾屋に移動 //その爆弾屋の爆破領域にある建物を全て破壊できるような爆破計画を決める //爆弾屋のマスと周囲1マスの計9マスにおける爆弾の集合を焼く //爆弾は最初に必ず全て買う必要がある(爆弾屋はすぐに吹っ飛ぶので) //周囲1マスの移動経路は8通りあるので最もコストの低い方法を使う vector<P> ans; int ans_cost = 0; int x = 0; int y = 0; set<int> 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: ちゃんと建物のコストを考慮してダイクストラする while(y > shopy[min_shop]) { ans.push_back({1, 0}); y--; if(A[x][y] == '.') ans_cost += 1; else ans_cost += 2; } while(y < shopy[min_shop]) { ans.push_back({1, 1}); y++; if(A[x][y] == '.') ans_cost += 1; else ans_cost += 2; } while(x > shopx[min_shop]) { ans.push_back({1, 2}); x--; if(A[x][y] == '.') ans_cost += 1; else ans_cost += 2; } while(x < shopx[min_shop]) { ans.push_back({1, 3}); x++; if(A[x][y] == '.') ans_cost += 1; else ans_cost += 2; } //爆破計画を焼く vector<int> best_bomb_set(9); vector<vector<int>> best_bomb_cnt(N, vector<int>(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(); int best_cost = 1e9; double start_temp = 2.0; double end_temp = 1.0; double temp = start_temp; int loop = 0; while(true) { if(loop%1000 == 0) { cerr << loop << " " << best_cost << "\n"; double time = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - start_time).count(); if(time > TIME_LIMIT) break; temp = start_temp + (end_temp - start_temp) * time / TIME_LIMIT; } loop++; vector<int> bomb_set(best_bomb_set); vector<vector<int>> bomb_cnt(best_bomb_cnt); //ランダムに爆弾を消す 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になるまで爆弾を追加する while(rest > 0) { int r = mt() % 9; int s = mt() % M; if(bomb_set[r] >> s & 1) 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; bomb_cnt[nx][ny]++; if(A[nx][ny] != '.' && best_shop_area[nx][ny] == min_shop && bomb_cnt[nx][ny] == 1) rest--; } } //コストの計算 int cost = 0; int have_bombs = 0; REP(i, 9) REP(j, M) if(bomb_set[i] >> j & 1) have_bombs++; REP(i, 9) { if(have_bombs == 0) break; REP(j, M) if(bomb_set[i] >> j & 1) { cost += C[j]; have_bombs--; } cost += 1 * (have_bombs + 1) * (have_bombs + 1); } double prob = exp(((double)best_cost - cost)*pow(0.1, temp)); if(cost <= best_cost || prob > (mt()%INF)/(double)INF) { best_cost = cost; best_bomb_set = bomb_set; best_bomb_cnt = bomb_cnt; } } //爆弾を購入 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(i, 9) { 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; if(i == 0 || i == 6 || i == 7) { ans.push_back({1, 0}); y--; if(A[x][y] == '.') ans_cost += 1 * (have_bombs + 1) * (have_bombs + 1); else ans_cost += 2 * (have_bombs + 1) * (have_bombs + 1); } else if(i == 2 || i == 3) { ans.push_back({1, 1}); y++; if(A[x][y] == '.') ans_cost += 1 * (have_bombs + 1) * (have_bombs + 1); else ans_cost += 2 * (have_bombs + 1) * (have_bombs + 1); } else if(i == 1) { ans.push_back({1, 2}); x--; if(A[x][y] == '.') ans_cost += 1 * (have_bombs + 1) * (have_bombs + 1); else ans_cost += 2 * (have_bombs + 1) * (have_bombs + 1); } else if(i == 4 || i == 5) { ans.push_back({1, 3}); x++; 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"; }