提出 #68712493


ソースコード 拡げる

#include <bits/stdc++.h>
using namespace std;

/*
  AHC052:配置(4..9) → 領域ロック×ローカル希望→グローバル投票(塗り 0..3) → 仕上げBFS
  - 0..3 は全ロボ共通で R(0), D(1), L(2), U(3)
  - 4..9 は「壁延長で領域抽出 → 中心に近い10領域を代表点化 → ハンガリー割当」
            → 最短路の要約(多数決)+軽い座標降下 で 配置フェーズ を最適化
  - 塗りは「owner固定(配置後の位置から多源BFS)+ 壁距離重み + 担当外ペナルティ」
      各手で各ロボの“望む向き”を計算 → 4ボタンを投票評価(違反最小・重み最大・新規被覆多)
  - 全被覆で即終了。出力に T は不要(ボタン割当K行 → 操作列を1行ずつ)
*/

// ===== 調整パラメータ =====
static const int SPREAD_REPEAT =2;     // 4444..9999 各ボタンの連打回数(固定)
static const int GREEDY_PASSES  = 3;    // 4..9 の座標降下の周回
static const int IDLE_STREAK_TO_BFS = 6;// 貪欲塗りで増分が止まったら仕上げへ
static const int SPREAD_SAFE_LIMIT = 2; // 配置フェーズの press で (moved==0 && gain==0) が続いたら抜ける保険

// 重み(端優先 & 担当外抑止)
static const double ALPHA_WALL = 5.0;   // 壁距離の逆数の重み
static const double BETA_EDGE  = 1.0;   // owner境界セルボーナス
static const double GAMMA_BASE = 0.5;   // 基本加点
static const double LEAK_PEN   = 0.0;   // 担当外ペナルティ(大きいほど外へ出にくい)
static const double STAY_PEN   = 0.15;  // その場留まりの微罰(動けるなら動かす)

// 方向: 0=R,1=D,2=L,3=U,4=S
static inline char dch(int d){ return d==0?'R': d==1?'D': d==2?'L': d==3?'U':'S'; }
static inline int  cid(int i,int j,int N){ return i*N + j; }

// ---- ハンガリー法(最小化) ----
struct Hungarian {
    int n; vector<vector<int>> a;
    Hungarian(int n): n(n), a(n, vector<int>(n,0)) {}
    void set_cost(int i,int j,int c){ a[i][j]=c; }
    pair<int, vector<int>> solve(){
        const int INF=1e9;
        vector<int> u(n+1), v(n+1), p(n+1), way(n+1);
        for(int i=1;i<=n;i++){
            p[0]=i; int j0=0; vector<int> minv(n+1,INF); vector<char> used(n+1,false);
            do{
                used[j0]=true; int i0=p[j0], delta=INF, j1=0;
                for(int j=1;j<=n;j++) if(!used[j]){
                    int cur=a[i0-1][j-1]-u[i0]-v[j];
                    if(cur<minv[j]){ minv[j]=cur; way[j]=j0; }
                    if(minv[j]<delta){ delta=minv[j]; j1=j; }
                }
                for(int j=0;j<=n;j++){
                    if(used[j]){ u[p[j]]+=delta; v[j]-=delta; }
                    else minv[j]-=delta;
                }
                j0=j1;
            }while(p[j0]!=0);
            do{ int j1=way[j0]; p[j0]=p[j1]; j0=j1; }while(j0);
        }
        vector<int> match(n,-1);
        for(int j=1;j<=n;j++) if(p[j]) match[p[j]-1]=j-1;
        return {-v[0], match};
    }
};

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N,M,K;                      // 期待: 30 10 10
    if(!(cin>>N>>M>>K)) return 0;
    vector<int> si(M), sj(M);
    for(int r=0;r<M;r++) cin>>si[r]>>sj[r];

    // vwall: N行×(N-1)列(左右の壁); hwall: (N-1)行×N列(上下の壁)
    vector<string> vwall(N);
    for(int i=0;i<N;i++) cin>>vwall[i];
    vector<string> hwall(N-1);
    for(int i=0;i<N-1;i++) cin>>hwall[i];

    const int NN=N*N;

    // ---- 壁考慮の1手遷移 NEXT と 隣接 NBR ----
    vector<array<int,5>> NEXT(NN);
    vector<array<int,4>> NBR(NN);
    auto cid = [&](int i,int j){ return i*N + j; };
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            int id=cid(i,j);
            // 右
            int ni=i, nj=j; if(j<N-1 && vwall[i][j]=='0') nj=j+1; NEXT[id][0]=cid(ni,nj);
            // 下
            ni=i; nj=j; if(i<N-1 && hwall[i][j]=='0') ni=i+1; NEXT[id][1]=cid(ni,nj);
            // 左
            ni=i; nj=j; if(j>0   && vwall[i][j-1]=='0') nj=j-1; NEXT[id][2]=cid(ni,nj);
            // 上
            ni=i; nj=j; if(i>0   && hwall[i-1][j]=='0') ni=i-1; NEXT[id][3]=cid(ni,nj);
            // 待機
            NEXT[id][4]=id;

            int ptr=0;
            for(int d=0; d<4; d++){ int v=NEXT[id][d]; if(v!=id) NBR[id][ptr++]=v; }
            for(;ptr<4;ptr++) NBR[id][ptr]=id;
        }
    }

    auto bfs_from = [&](int s){
        vector<int> dist(NN, INT_MAX);
        deque<int> dq; dist[s]=0; dq.push_back(s);
        while(!dq.empty()){
            int u=dq.front(); dq.pop_front();
            for(int k=0;k<4;k++){
                int v=NBR[u][k]; if(v==u) continue;
                if(dist[v] > dist[u]+1){ dist[v]=dist[u]+1; dq.push_back(v); }
            }
        }
        return dist;
    };

    // ---- 既存の壁を列/行方向に“延長”して分割境界(0..N)を作る ----
    auto build_cuts = [&](int N,
                          const vector<string>& vwall,
                          const vector<string>& hwall){
        vector<int> colCuts = {0, N};
        for(int j=0;j<N-1;j++){
            bool has=false; for(int i=0;i<N;i++) if(vwall[i][j]=='1'){ has=true; break; }
            if(has) colCuts.push_back(j+1);
        }
        sort(colCuts.begin(), colCuts.end());
        colCuts.erase(unique(colCuts.begin(), colCuts.end()), colCuts.end());

        vector<int> rowCuts = {0, N};
        for(int i=0;i<N-1;i++){
            bool has=false; for(int j=0;j<N;j++) if(hwall[i][j]=='1'){ has=true; break; }
            if(has) rowCuts.push_back(i+1);
        }
        sort(rowCuts.begin(), rowCuts.end());
        rowCuts.erase(unique(rowCuts.begin(), rowCuts.end()), rowCuts.end());
        return pair<vector<int>,vector<int>>(rowCuts, colCuts);
    };

    auto pick_center_in_rect = [&](int i0,int i1,int j0,int j1){
        int ci_geo=(i0+i1)/2, cj_geo=(j0+j1)/2;
        int bestI=i0, bestJ=j0, bestScore=INT_MIN;
        for(int i=i0;i<=i1;i++){
            for(int j=j0;j<=j1;j++){
                int toEdge = min({i-i0, i1-i, j-j0, j1-j});
                int nearGeo = -(abs(i-ci_geo)+abs(j-cj_geo));
                int sc = 10*toEdge + nearGeo; // 端から離す:10、幾何中心:1
                if(sc>bestScore){ bestScore=sc; bestI=i; bestJ=j; }
            }
        }
        return pair<int,int>(bestI,bestJ);
    };

    // ---- ターゲット(中心に近い領域の代表点 10 個)を作る ----
    auto [rowCuts, colCuts] = build_cuts(N, vwall, hwall);

    struct Rect { int i0,i1,j0,j1, ci,cj, centerDist; };
    vector<Rect> rects;
    int GI=(N-1)/2, GJ=(N-1)/2;
    for(int a=0; a+1<(int)rowCuts.size(); a++){
        for(int b=0; b+1<(int)colCuts.size(); b++){
            int i0=rowCuts[a], i1=rowCuts[a+1]-1;
            int j0=colCuts[b], j1=colCuts[b+1]-1;
            if(i0>i1 || j0>j1) continue;
            auto [ci,cj]=pick_center_in_rect(i0,i1,j0,j1);
            int cd=abs(ci-GI)+abs(cj-GJ);
            rects.push_back({i0,i1,j0,j1,ci,cj,cd});
        }
    }
    sort(rects.begin(),rects.end(),[&](const Rect&A,const Rect&B){return A.centerDist<B.centerDist;});

    vector<pair<int,int>> targets;
    for(int k=0;k<(int)rects.size() && (int)targets.size()<10; k++)
        targets.emplace_back(rects[k].ci, rects[k].cj);
    if((int)targets.size()<10){
        // フォールバック:2x5 の中点で補完
        int rc2[3]={0,N/2,N}, cc2[6]={0,N/5,2*N/5,3*N/5,4*N/5,N};
        for(int rb=0; rb<2 && (int)targets.size()<10; rb++)
            for(int cb=0; cb<5 && (int)targets.size()<10; cb++){
                int i0=rc2[rb], i1=rc2[rb+1]-1, j0=cc2[cb], j1=cc2[cb+1]-1;
                auto [ci,cj]=pick_center_in_rect(i0,i1,j0,j1);
                if(find(targets.begin(),targets.end(),make_pair(ci,cj))==targets.end())
                    targets.emplace_back(ci,cj);
            }
    }

    // ---- BFS距離 → ハンガリー割当 ----
    vector<vector<int>> distR(M);
    for(int r=0;r<M;r++) distR[r]=bfs_from(cid(si[r],sj[r]));
    vector<vector<int>> distT(10);
    for(int t=0;t<10;t++) distT[t]=bfs_from(cid(targets[t].first, targets[t].second));

    struct Hungarian {
        int n; vector<vector<int>> a;
        Hungarian(int n): n(n), a(n, vector<int>(n,0)) {}
        void set_cost(int i,int j,int c){ a[i][j]=c; }
        pair<int, vector<int>> solve(){
            const int INF=1e9;
            vector<int> u(n+1), v(n+1), p(n+1), way(n+1);
            for(int i=1;i<=n;i++){
                p[0]=i; int j0=0; vector<int> minv(n+1,INF); vector<char> used(n+1,false);
                do{
                    used[j0]=true; int i0=p[j0], delta=INF, j1=0;
                    for(int j=1;j<=n;j++) if(!used[j]){
                        int cur=a[i0-1][j-1]-u[i0]-v[j];
                        if(cur<minv[j]){ minv[j]=cur; way[j]=j0; }
                        if(minv[j]<delta){ delta=minv[j]; j1=j; }
                    }
                    for(int j=0;j<=n;j++){
                        if(used[j]){ u[p[j]]+=delta; v[j]-=delta; }
                        else minv[j]-=delta;
                    }
                    j0=j1;
                }while(p[j0]!=0);
                do{ int j1=way[j0]; p[j0]=p[j1]; j0=j1; }while(j0);
            }
            vector<int> match(n,-1);
            for(int j=1;j<=n;j++) if(p[j]) match[p[j]-1]=j-1;
            return {-v[0], match};
        }
    };

    Hungarian hung(M); // M==10
    for(int r=0;r<M;r++){
        for(int t=0;t<10;t++){
            int c = distR[r][ cid(targets[t].first, targets[t].second) ];
            if(c==INT_MAX) c=100000;
            hung.set_cost(r,t,c);
        }
    }
    auto [assignCost, matchRT] = hung.solve(); // matchRT[r] in [0,9]

    // ---- ボタン割当テーブル ACT[b][r] ----
    vector<array<int,128>> ACT(K);
    for(int b=0;b<K;b++) for(int r=0;r<M;r++) ACT[b][r]=4; // S 初期化
    // 0..3 は全ロボ共通で R D L U(塗り用)
    for(int r=0;r<M;r++){ ACT[0][r]=0; ACT[1][r]=1; ACT[2][r]=2; ACT[3][r]=3; }
    auto dch=[&](int d){ return d==0?'R': d==1?'D': d==2?'L': d==3?'U':'S'; };

    // r番ロボの start→target の最短路(方向列 0/1/2/3)
    auto shortest_dir_path = [&](int r)->vector<int>{
        int s = cid(si[r], sj[r]);
        int t = cid(targets[ matchRT[r] ].first, targets[ matchRT[r] ].second);
        vector<int> pre(NN,-1), pd(NN,-1);
        deque<int> dq; dq.push_back(s); pre[s]=s;
        while(!dq.empty()){
            int u=dq.front(); dq.pop_front();
            if(u==t) break;
            for(int d=0; d<4; d++){
                int v=NEXT[u][d]; if(v==u) continue;
                if(pre[v]==-1){ pre[v]=u; pd[v]=d; dq.push_back(v); }
            }
        }
        vector<int> path;
        if(pre[t]==-1) return path;
        for(int u=t; u!=s; u=pre[u]) path.push_back(pd[u]);
        reverse(path.begin(), path.end());
        return path;
    };

    vector<vector<int>> paths(M);
    for(int r=0;r<M;r++) paths[r]=shortest_dir_path(r);

    // 調整パラメータ
    static const int SPREAD_REPEAT = 2;     // 4444..9999 各ボタンの連打回数(固定)
    static const int GREEDY_PASSES  = 10;    // 4..9 の座標降下の周回
    static const int IDLE_STREAK_TO_BFS = 0;// 貪欲塗りで増分が止まったら仕上げへ
    static const int SPREAD_SAFE_LIMIT = 10; // 配置フェーズの press で (moved==0 && gain==0) が続いたら抜ける保険

    // 4..9 を「6ブロック×SPREAD_REPEAT 手」の多数決で初期化
    for(int r=0;r<M;r++){
        for(int k=0;k<6;k++){
            int b=4+k;
            array<int,5> cnt={0,0,0,0,0};
            for(int t=0;t<SPREAD_REPEAT; t++){
                int idx=k*SPREAD_REPEAT+t;
                if(idx<(int)paths[r].size()) cnt[ paths[r][idx] ]++;
                else cnt[4]++;
            }
            int best=4; for(int d=0; d<4; d++) if(cnt[d]>cnt[best]) best=d;
            ACT[b][r]=best;
        }
    }

    // 座標降下(4..9 のみ評価):4444..9999 適用後の Σ距離(target_r, 最終pos_r) を最小化
    vector<int> spread_seq; spread_seq.reserve(6*SPREAD_REPEAT);
    for(int b=4;b<=9;b++) for(int t=0;t<SPREAD_REPEAT;t++) spread_seq.push_back(b);

    auto eval_spread = [&](const vector<array<int,128>>& A)->long long{
        array<int,128> pos{};
        for(int r=0;r<M;r++) pos[r]=cid(si[r],sj[r]);
        for(int btn: spread_seq)
            for(int r=0;r<M;r++) pos[r]=NEXT[pos[r]][ A[btn][r] ];
        long long sum=0;
        for(int r=0;r<M;r++){
            int t = matchRT[r];
            int d = distT[t][ pos[r] ];
            if(d==INT_MAX) d=100000;
            sum += d;
        }
        return sum; // 小さいほど良
    };

    auto greedy_coordinate_descent = [&](){
        long long cur = eval_spread(ACT);
        bool improved=true;
        for(int pass=0; pass<GREEDY_PASSES && improved; ++pass){
            improved=false;
            for(int r=0;r<M;r++){
                for(int b=4;b<=9;b++){
                    int old = ACT[b][r];
                    int bestAct=old; long long bestVal=cur;
                    for(int cand=0;cand<=4;cand++){
                        if(cand==old) continue;
                        ACT[b][r]=cand;
                        long long val=eval_spread(ACT);
                        if(val<bestVal){ bestVal=val; bestAct=cand; }
                    }
                    if(bestAct!=old){ ACT[b][r]=bestAct; cur=bestVal; improved=true; }
                    else ACT[b][r]=old;
                }
            }
        }
    };
    greedy_coordinate_descent();

    // === (追加) 0..3 のロボ別変換を「多数決ロックイン」で最適化 =====================
    const int MAP_PROBE_STEPS    = 320; // 仮本番の手数
    const int MAP_LOCK_ROUNDS    = 3;   // ロックのラウンド数
    const int MAP_LOCK_PER_ROUND = 1;   // 1ラウンドで固定するロボ数
    const double LOCK_MIN_FRAC   = 0.55;// ロック閾値(賛成率)

    const int TBL[8][4] = {
        {0,1,2,3}, {1,2,3,0}, {2,3,0,1}, {3,0,1,2},
        {2,1,0,3}, {1,0,3,2}, {0,3,2,1}, {3,2,1,0}
    };
    auto next_with_map = [&](int p, int mapi, int btn)->int{
        return NEXT[p][ TBL[mapi][btn] ];
    };

    // 4..9 をオフライン適用して配置後を得る
    vector<int> pos_sim(M); vector<char> seen_sim(NN,0); int covered_sim=0;
    for(int r=0;r<M;r++){ int p=cid(si[r],sj[r]); pos_sim[r]=p; if(!seen_sim[p]){ seen_sim[p]=1; covered_sim++; } }
    for(int btn: spread_seq){
        for(int r=0;r<M;r++) pos_sim[r]=NEXT[pos_sim[r]][ ACT[btn][r] ];
        for(int r=0;r<M;r++){ int p=pos_sim[r]; if(!seen_sim[p]){ seen_sim[p]=1; covered_sim++; } }
    }

    // owner_fix(配置後基準)
    vector<int> owner_fix(NN,-1), distOwn_fix(NN,INT_MAX);
    auto build_owner_fix = [&](){
        fill(owner_fix.begin(),owner_fix.end(),-1);
        fill(distOwn_fix.begin(),distOwn_fix.end(),INT_MAX);
        deque<int> dq;
        for(int r=0;r<M;r++){
            int s=pos_sim[r];
            if(distOwn_fix[s]>0){ distOwn_fix[s]=0; owner_fix[s]=r; dq.push_back(s); }
            else if(owner_fix[s]>r) owner_fix[s]=r;
        }
        while(!dq.empty()){
            int u=dq.front(); dq.pop_front();
            for(int k=0;k<4;k++){
                int v=NBR[u][k]; if(v==u) continue;
                if(distOwn_fix[v] > distOwn_fix[u]+1){
                    distOwn_fix[v]=distOwn_fix[u]+1; owner_fix[v]=owner_fix[u]; dq.push_back(v);
                }else if(distOwn_fix[v]==distOwn_fix[u]+1 && owner_fix[v]>owner_fix[u]){
                    owner_fix[v]=owner_fix[u];
                }
            }
        }
    };
    build_owner_fix();

    // 壁距離(外周&実壁に接するセルから)
    vector<int> distWall_fix(NN, INT_MAX);
    auto build_wall_distance_fix = [&](){
        deque<int> dq;
        auto touch_wall = [&](int i,int j)->bool{
            if(i==0||i==N-1||j==0||j==N-1) return true;
            if(vwall[i][j]=='1') return true;
            if(j>0 && vwall[i][j-1]=='1') return true;
            if(hwall[i][j]=='1') return true;
            if(i>0 && hwall[i-1][j]=='1') return true;
            return false;
        };
        for(int i=0;i<N;i++)for(int j=0;j<N;j++){
            int p=cid(i,j);
            if(touch_wall(i,j)){ distWall_fix[p]=0; dq.push_back(p); }
        }
        while(!dq.empty()){
            int u=dq.front(); dq.pop_front();
            for(int k=0;k<4;k++){
                int v=NBR[u][k]; if(v==u) continue;
                if(distWall_fix[v] > distWall_fix[u]+1){
                    distWall_fix[v]=distWall_fix[u]+1;
                    dq.push_back(v);
                }
            }
        }
    };
    build_wall_distance_fix();

    // owner 境界マーキング
    vector<char> isBorder_fix(NN,0);
    auto mark_owner_border_fix = [&](){
        fill(isBorder_fix.begin(),isBorder_fix.end(),0);
        for(int p=0;p<NN;p++){
            int r=owner_fix[p]; if(r<0) continue;
            for(int k=0;k<4;k++){
                int v=NBR[p][k]; if(v==p) continue;
                if(owner_fix[v]!=r){ isBorder_fix[p]=1; break; }
            }
        }
    };
    mark_owner_border_fix();

    // 重み(端優先 & 担当外抑止)
    static const double ALPHA_WALL = 5.0;
    static const double BETA_EDGE  = 1.0;
    static const double GAMMA_BASE = 0.5;
    static const double LEAK_PEN   = 2.0;
    static const double STAY_PEN   = 0.15;

    auto weight_of_fix = [&](int p)->double{
        double w = GAMMA_BASE + ALPHA_WALL / (1.0 + (double)distWall_fix[p]);
        if(isBorder_fix[p]) w += BETA_EDGE;
        return w;
    };

    // probe(仮本番)
    auto local_best_dir_probe = [&](int posr, int r, int mapi, const vector<char>& seen)->pair<int,double>{
        double bestSc=-1e100; int bestB=0; bool haveIn=false;
        for(int b=0;b<4;b++){
            int q = next_with_map(posr, mapi, b);
            if(q!=posr && owner_fix[q]==r){ haveIn=true; break; }
        }
        for(int b=0;b<4;b++){
            int q = next_with_map(posr, mapi, b);
            double sc=0.0;
            if(q==posr) sc-=STAY_PEN;
            if(!seen[q]) sc += weight_of_fix(q);
            if(owner_fix[q]!=r){ sc -= haveIn ? LEAK_PEN : (0.3*LEAK_PEN); }
            if(sc>bestSc){ bestSc=sc; bestB=b; }
        }
        return {bestB, bestSc};
    };

    auto eval_button_global_probe = [&](int b,
                                        const vector<int>& posv,
                                        const vector<int>& mapv,
                                        const vector<char>& seen,
                                        const vector<pair<int,double>>& wish){
        int viol=0,gain=0,agree=0; double sumScore=0.0;
        static vector<char> mark; mark.assign(NN,0);
        for(int r=0;r<M;r++){
            int q = next_with_map(posv[r], mapv[r], b);
            if(wish[r].first==b) agree++;
            if(q!=posv[r] && owner_fix[q]!=r) viol++;
            double sc=0.0;
            if(q==posv[r]) sc-=STAY_PEN;
            if(!seen[q]){ sc+=weight_of_fix(q); mark[q]=1; }
            if(q!=posv[r] && owner_fix[q]!=r) sc-=LEAK_PEN;
            sumScore+=sc;
        }
        for(int p=0;p<NN;p++) if(mark[p] && !seen[p]) gain++;
        return tuple<int,int,int,double>(viol,gain,agree,sumScore);
    };

    auto better_tuple = [&](const tuple<int,int,int,double>& A, const tuple<int,int,int,double>& B){
        auto [va,ga,aa,sa]=A; auto [vb,gb,ab,sb]=B;
        if(va!=vb) return va<vb;   // 違反最小
        if(sa!=sb) return sa>sb;   // スコア最大
        if(ga!=gb) return ga>gb;   // 新規被覆最大
        if(aa!=ab) return aa>ab;   // 賛成多数
        return true;
    };

    struct ProbeResult{ vector<int> press_seq; vector<int> agree_cnt; };

    auto run_probe = [&](const vector<int>& mapv)->ProbeResult{
        vector<int> pos=pos_sim; vector<char> seen=seen_sim;
        vector<int> agree(M,0); vector<int> presses; presses.reserve(MAP_PROBE_STEPS);
        for(int step=0; step<MAP_PROBE_STEPS; ++step){
            vector<pair<int,double>> wish(M);
            for(int r=0;r<M;r++) wish[r]=local_best_dir_probe(pos[r], r, mapv[r], seen);
            array<tuple<int,int,int,double>,4> ev;
            for(int b=0;b<4;b++) ev[b]=eval_button_global_probe(b,pos,mapv,seen,wish);
            int best=0;
            for(int b=1;b<4;b++) if(better_tuple(ev[b],ev[best])) best=b;
            for(int r=0;r<M;r++) if(wish[r].first==best) agree[r]++;
            // 適用
            for(int r=0;r<M;r++) pos[r]=next_with_map(pos[r], mapv[r], best);
            for(int r=0;r<M;r++){ int p=pos[r]; if(!seen[p]) seen[p]=1; }
            presses.push_back(best);
        }
        return {presses, agree};
    };

    vector<int> mapIdx(M,0), locked(M,0);
    for(int round=0; round<MAP_LOCK_ROUNDS; ++round){
        ProbeResult pr = run_probe(mapIdx);
        vector<int> idxs(M); iota(idxs.begin(), idxs.end(), 0);
        sort(idxs.begin(), idxs.end(), [&](int a,int b){
            if(locked[a]!=locked[b]) return locked[a]>locked[b];
            return pr.agree_cnt[a] > pr.agree_cnt[b];
        });
        int did=0;
        double bestFrac = 0.0;
        for(int r=0;r<M;r++) bestFrac = max(bestFrac, pr.agree_cnt[r] / max(1.0, (double)MAP_PROBE_STEPS));
        for(int id: idxs){
            if(locked[id]) continue;
            double frac = pr.agree_cnt[id] / max(1.0, (double)MAP_PROBE_STEPS);
            if(frac >= max(LOCK_MIN_FRAC, min(0.9, bestFrac*0.9))){
                locked[id]=1; if(++did>=MAP_LOCK_PER_ROUND) break;
            }
        }
        for(int r=0;r<M;r++) if(!locked[r]){
            int bestIdx = mapIdx[r];
            double bestScore = -1e100;
            for(int cand=0;cand<8;cand++){
                int posr = pos_sim[r];
                double s=0.0;
                for(int b: pr.press_seq){
                    int q = next_with_map(posr, cand, b);
                    if(q==posr) s -= STAY_PEN;
                    if(owner_fix[q]==r) s += 1.0; else s -= 0.5*LEAK_PEN;
                    s += 0.2 * (ALPHA_WALL / (1.0 + (double)distWall_fix[q]));
                    posr = q;
                }
                if(s>bestScore){ bestScore=s; bestIdx=cand; }
            }
            mapIdx[r]=bestIdx;
        }
        if(accumulate(locked.begin(), locked.end(), 0)==M) break;
    }
    // 決定した変換を ACT[0..3] に反映
    for(int r=0;r<M;r++) for(int b=0;b<4;b++) ACT[b][r] = TBL[ mapIdx[r] ][b];

    // ★ ここで「実方向→押すボタン」逆写像を作る(仕上げBFSで使用)
    vector<array<int,4>> invBtn(M);
    for(int r=0;r<M;r++){
        for(int d=0; d<4; d++) invBtn[r][d]=0;
        for(int b=0; b<4; b++){
            int dir = ACT[b][r];       // ボタンbの実方向
            if(dir<4) invBtn[r][dir] = b; // 実方向→ボタン
        }
    }
    // === ここまで 0..3 最適化 =====================================================

    // ---- ボタン割当(K行)を出力(Tは出さない) ----
    for(int b=0;b<K;b++){
        for(int r=0;r<M;r++){
            if(r) cout<<' ';
            cout<<dch(ACT[b][r]);
        }
        cout<<'\n';
    }

    // ---- 操作適用ユーティリティ ----
    vector<int> pos(M); vector<char> seen(NN,0);
    int covered=0;
    for(int r=0;r<M;r++){ int p=cid(si[r],sj[r]); pos[r]=p; if(!seen[p]){ seen[p]=1; covered++; } }

    const int LIMIT = 2*N*N;
    int used=0;
    auto press_button = [&](int b)->pair<int,int>{ // (moved, gain)
        if(used>=LIMIT) return pair<int,int>(0,0);
        int moved=0,gain=0;
        for(int r=0;r<M;r++){
            int before=pos[r];
            int after = NEXT[before][ ACT[b][r] ];
            if(after!=before) moved++;
            pos[r]=after;
        }
        for(int r=0;r<M;r++){ int p=pos[r]; if(!seen[p]){ seen[p]=1; gain++; } }
        covered+=gain;
        cout<<b<<"\n";
        used++;
        return {moved,gain};
    };

    // === 1) 配置フェーズ(4444..9999) ===
    {
        int dead=0;
        for(int btn: spread_seq){
            auto [mv,gn] = press_button(btn);
            if(mv==0 && gn==0){ if(++dead>=SPREAD_SAFE_LIMIT) break; }
            else dead=0;
            if(covered==NN || used>=LIMIT) return 0;
        }
    }

    // === 2) 領域ロック(owner固定) ===
    vector<int> owner(NN,-1), distOwn(NN,INT_MAX);
    auto lock_owner = [&](){
        fill(owner.begin(),owner.end(),-1);
        fill(distOwn.begin(),distOwn.end(),INT_MAX);
        deque<int> dq;
        for(int r=0;r<M;r++){
            int s=pos[r];
            if(distOwn[s]>0){ distOwn[s]=0; owner[s]=r; dq.push_back(s); }
            else if(owner[s]>r) owner[s]=r;
        }
        while(!dq.empty()){
            int u=dq.front(); dq.pop_front();
            for(int k=0;k<4;k++){
                int v=NBR[u][k]; if(v==u) continue;
                if(distOwn[v] > distOwn[u]+1){
                    distOwn[v]=distOwn[u]+1; owner[v]=owner[u]; dq.push_back(v);
                }else if(distOwn[v]==distOwn[u]+1 && owner[v]>owner[u]){
                    owner[v]=owner[u];
                }
            }
        }
    };
    lock_owner();

    // 壁距離場(外周+実壁に接するセルをソース)
    vector<int> distWall(NN, INT_MAX);
    auto build_wall_distance = [&](){
        deque<int> dq;
        auto touch_wall = [&](int i,int j)->bool{
            if(i==0 || i==N-1 || j==0 || j==N-1) return true;
            if(vwall[i][j]=='1') return true;              // 右側に壁
            if(j>0 && vwall[i][j-1]=='1') return true;     // 左側に壁
            if(hwall[i][j]=='1') return true;              // 下側に壁
            if(i>0 && hwall[i-1][j]=='1') return true;     // 上側に壁
            return false;
        };
        for(int i=0;i<N;i++)for(int j=0;j<N;j++){
            int p=cid(i,j);
            if(touch_wall(i,j)){ distWall[p]=0; dq.push_back(p); }
        }
        while(!dq.empty()){
            int u=dq.front(); dq.pop_front();
            for(int k=0;k<4;k++){
                int v=NBR[u][k]; if(v==u) continue;
                if(distWall[v] > distWall[u]+1){
                    distWall[v] = distWall[u]+1;
                    dq.push_back(v);
                }
            }
        }
    };
    build_wall_distance();

    // owner境界セルマーキング(ボーナス用)
    vector<char> isBorder(NN,0);
    auto mark_owner_border = [&](){
        fill(isBorder.begin(), isBorder.end(), 0);
        for(int p=0;p<NN;p++){
            int r=owner[p]; if(r<0) continue;
            for(int k=0;k<4;k++){
                int v=NBR[p][k]; if(v==p) continue;
                if(owner[v]!=r){ isBorder[p]=1; break; }
            }
        }
    };
    mark_owner_border();

    auto weight_of = [&](int p)->double{
        double w = GAMMA_BASE + ALPHA_WALL / (1.0 + (double)distWall[p]);
        if(isBorder[p]) w += BETA_EDGE;
        return w;
    };

    // ローカル希望:各ロボが「担当内で」「一番おいしい」方向を選ぶ
    auto local_best_dir = [&](int r)->pair<int,double>{
        int cur = pos[r];
        double bestSc = -1e100; int bestB = 0;
        bool exists_in_owner_move=false;
        for(int b=0;b<4;b++){
            int q = NEXT[cur][ ACT[b][r] ];
            if(q!=cur && owner[q]==r){ exists_in_owner_move=true; break; }
        }
        for(int b=0;b<4;b++){
            int q = NEXT[cur][ ACT[b][r] ];
            double sc = 0.0;
            if(q==cur) sc -= STAY_PEN;
            if(!seen[q]) sc += weight_of(q);
            if(owner[q]!=r){
                sc -= exists_in_owner_move ? LEAK_PEN : (0.3 * LEAK_PEN);
            }
            if(sc > bestSc){ bestSc=sc; bestB=b; }
        }
        return {bestB, bestSc};
    };

    // ボタン b を全体に押したときの評価
    auto eval_button_global = [&](int b,
                                  const vector<pair<int,double>>& wish)->tuple<int,int,int,double>{
        int agree=0, viol=0, moved=0; double sumScore=0.0;
        static vector<char> mark; mark.assign(NN,0);
        for(int r=0;r<M;r++){
            int q = NEXT[pos[r]][ ACT[b][r] ];
            if(wish[r].first==b) agree++;
            if(q!=pos[r]) moved++;
            if(owner[q]!=r && q!=pos[r]) viol++;
            double sc = 0.0;
            if(q==pos[r]) sc -= STAY_PEN;
            if(!seen[q]) sc += weight_of(q);
            if(owner[q]!=r && q!=pos[r]) sc -= LEAK_PEN;
            sumScore += sc;
            mark[q]=1;
        }
        int gain=0;
        for(int p=0;p<NN;p++) if(mark[p] && !seen[p]) gain++;
        return {viol, gain, agree, sumScore};
    };

    // === 3) 領域ロック × ローカル希望 → グローバル投票(塗り) ===
    int idle_streak=0;
    while(covered<NN && used<LIMIT){
        vector<pair<int,double>> wish(M);
        for(int r=0;r<M;r++) wish[r] = local_best_dir(r);
        array<tuple<int,int,int,double>,4> ev;
        for(int b=0;b<4;b++) ev[b] = eval_button_global(b, wish);
        int best = 0;
        auto better = [&](int x, int y){
            auto [vx, gx, ax, sx] = ev[x];
            auto [vy, gy, ay, sy] = ev[y];
            if(vx != vy) return vx < vy;
            if(sx != sy) return sx > sy;
            if(gx != gy) return gx > gy;  // gain大
            if(ax != ay) return ax > ay;  // 賛成多数
            return x < y;
        };
        for(int b=1;b<4;b++) if(better(b, best)) best=b;

        int before = covered;
        auto [mv,gn] = press_button(best);
        (void)mv;
        if(covered==before) idle_streak++; else idle_streak=0;
        if(idle_streak >= IDLE_STREAK_TO_BFS) break;
    }

    if(covered==NN || used>=LIMIT) return 0;

    // === 4) 仕上げ:最寄り未訪1マスを選び、その最寄りロボ r★ の最短路を「r★に合うボタン」で押す ===
    auto multi_source_from_robots = [&](
        const vector<int>& pos,
        vector<int>& ownerTmp, vector<int>& distTmp
    ){
        ownerTmp.assign(NN,-1);
        distTmp.assign(NN, INT_MAX);
        deque<int> dq;
        for(int r=0;r<M;r++){
            int s=pos[r];
            if(distTmp[s]>0){ distTmp[s]=0; ownerTmp[s]=r; dq.push_back(s); }
            else if(ownerTmp[s]>r) ownerTmp[s]=r;
        }
        while(!dq.empty()){
            int u=dq.front(); dq.pop_front();
            for(int k=0;k<4;k++){
                int v=NBR[u][k]; if(v==u) continue;
                if(distTmp[v] > distTmp[u]+1){
                    distTmp[v]=distTmp[u]+1; ownerTmp[v]=ownerTmp[u]; dq.push_back(v);
                }else if(distTmp[v]==distTmp[u]+1 && ownerTmp[v]>ownerTmp[u]){
                    ownerTmp[v]=ownerTmp[u];
                }
            }
        }
    };

    vector<int> owner2, dist2;
    while(covered<NN && used<LIMIT){
        multi_source_from_robots(pos, owner2, dist2);
        int pstar=-1, best=INT_MAX;
        for(int p=0;p<NN;p++) if(!seen[p] && dist2[p]<best){ best=dist2[p]; pstar=p; }
        if(pstar==-1 || best==INT_MAX) break;

        int rstar = owner2[pstar];

        // rstar 単一源BFSで経路復元(絶対方向 0..3)
        vector<int> pre(NN,-1), pd(NN,-1);
        deque<int> q;
        int s = pos[rstar];
        q.push_back(s); pre[s]=s;
        while(!q.empty()){
            int u=q.front(); q.pop_front();
            if(u==pstar) break;
            for(int d=0; d<4; d++){
                int v=NEXT[u][d]; if(v==u) continue;
                if(pre[v]==-1){ pre[v]=u; pd[v]=d; q.push_back(v); }
            }
        }
        if(pre[pstar]==-1) break; // 保険

        vector<int> path;
        for(int u=pstar; u!=s; u=pre[u]) path.push_back(pd[u]);
        reverse(path.begin(), path.end());

        // ★ 絶対方向 → r★ に対して押すべきボタンへ変換して押下
        for(int dAbs: path){
            int b = invBtn[rstar][dAbs];
            auto [mv,gn] = press_button(b);
            (void)mv; (void)gn;
            if(covered==NN || used>=LIMIT) break;
        }
    }

    return 0;
}

提出情報

提出日時
問題 A - Single Controller Multiple Robots
ユーザ koshinM
言語 C++ 17 (gcc 12.2)
得点 353853
コード長 33089 Byte
結果 AC
実行時間 11 ms
メモリ 3832 KiB

ジャッジ結果

セット名 test_ALL
得点 / 配点 353853 / 405000
結果
AC × 150
セット名 テストケース
test_ALL test_0000.txt, test_0001.txt, test_0002.txt, test_0003.txt, test_0004.txt, test_0005.txt, test_0006.txt, test_0007.txt, test_0008.txt, test_0009.txt, test_0010.txt, test_0011.txt, test_0012.txt, test_0013.txt, test_0014.txt, test_0015.txt, test_0016.txt, test_0017.txt, test_0018.txt, test_0019.txt, test_0020.txt, test_0021.txt, test_0022.txt, test_0023.txt, test_0024.txt, test_0025.txt, test_0026.txt, test_0027.txt, test_0028.txt, test_0029.txt, test_0030.txt, test_0031.txt, test_0032.txt, test_0033.txt, test_0034.txt, test_0035.txt, test_0036.txt, test_0037.txt, test_0038.txt, test_0039.txt, test_0040.txt, test_0041.txt, test_0042.txt, test_0043.txt, test_0044.txt, test_0045.txt, test_0046.txt, test_0047.txt, test_0048.txt, test_0049.txt, test_0050.txt, test_0051.txt, test_0052.txt, test_0053.txt, test_0054.txt, test_0055.txt, test_0056.txt, test_0057.txt, test_0058.txt, test_0059.txt, test_0060.txt, test_0061.txt, test_0062.txt, test_0063.txt, test_0064.txt, test_0065.txt, test_0066.txt, test_0067.txt, test_0068.txt, test_0069.txt, test_0070.txt, test_0071.txt, test_0072.txt, test_0073.txt, test_0074.txt, test_0075.txt, test_0076.txt, test_0077.txt, test_0078.txt, test_0079.txt, test_0080.txt, test_0081.txt, test_0082.txt, test_0083.txt, test_0084.txt, test_0085.txt, test_0086.txt, test_0087.txt, test_0088.txt, test_0089.txt, test_0090.txt, test_0091.txt, test_0092.txt, test_0093.txt, test_0094.txt, test_0095.txt, test_0096.txt, test_0097.txt, test_0098.txt, test_0099.txt, test_0100.txt, test_0101.txt, test_0102.txt, test_0103.txt, test_0104.txt, test_0105.txt, test_0106.txt, test_0107.txt, test_0108.txt, test_0109.txt, test_0110.txt, test_0111.txt, test_0112.txt, test_0113.txt, test_0114.txt, test_0115.txt, test_0116.txt, test_0117.txt, test_0118.txt, test_0119.txt, test_0120.txt, test_0121.txt, test_0122.txt, test_0123.txt, test_0124.txt, test_0125.txt, test_0126.txt, test_0127.txt, test_0128.txt, test_0129.txt, test_0130.txt, test_0131.txt, test_0132.txt, test_0133.txt, test_0134.txt, test_0135.txt, test_0136.txt, test_0137.txt, test_0138.txt, test_0139.txt, test_0140.txt, test_0141.txt, test_0142.txt, test_0143.txt, test_0144.txt, test_0145.txt, test_0146.txt, test_0147.txt, test_0148.txt, test_0149.txt
ケース名 結果 実行時間 メモリ
test_0000.txt AC 9 ms 3600 KiB
test_0001.txt AC 9 ms 3564 KiB
test_0002.txt AC 9 ms 3600 KiB
test_0003.txt AC 9 ms 3632 KiB
test_0004.txt AC 8 ms 3564 KiB
test_0005.txt AC 8 ms 3636 KiB
test_0006.txt AC 8 ms 3772 KiB
test_0007.txt AC 7 ms 3564 KiB
test_0008.txt AC 9 ms 3708 KiB
test_0009.txt AC 8 ms 3620 KiB
test_0010.txt AC 8 ms 3564 KiB
test_0011.txt AC 11 ms 3704 KiB
test_0012.txt AC 9 ms 3628 KiB
test_0013.txt AC 8 ms 3624 KiB
test_0014.txt AC 8 ms 3628 KiB
test_0015.txt AC 9 ms 3828 KiB
test_0016.txt AC 8 ms 3828 KiB
test_0017.txt AC 8 ms 3624 KiB
test_0018.txt AC 9 ms 3708 KiB
test_0019.txt AC 9 ms 3708 KiB
test_0020.txt AC 7 ms 3708 KiB
test_0021.txt AC 10 ms 3832 KiB
test_0022.txt AC 8 ms 3764 KiB
test_0023.txt AC 8 ms 3692 KiB
test_0024.txt AC 9 ms 3764 KiB
test_0025.txt AC 8 ms 3684 KiB
test_0026.txt AC 8 ms 3600 KiB
test_0027.txt AC 9 ms 3760 KiB
test_0028.txt AC 9 ms 3632 KiB
test_0029.txt AC 9 ms 3600 KiB
test_0030.txt AC 8 ms 3692 KiB
test_0031.txt AC 9 ms 3632 KiB
test_0032.txt AC 9 ms 3832 KiB
test_0033.txt AC 8 ms 3708 KiB
test_0034.txt AC 8 ms 3684 KiB
test_0035.txt AC 8 ms 3628 KiB
test_0036.txt AC 9 ms 3772 KiB
test_0037.txt AC 8 ms 3684 KiB
test_0038.txt AC 8 ms 3600 KiB
test_0039.txt AC 7 ms 3708 KiB
test_0040.txt AC 9 ms 3708 KiB
test_0041.txt AC 8 ms 3684 KiB
test_0042.txt AC 9 ms 3712 KiB
test_0043.txt AC 8 ms 3760 KiB
test_0044.txt AC 8 ms 3764 KiB
test_0045.txt AC 7 ms 3708 KiB
test_0046.txt AC 9 ms 3684 KiB
test_0047.txt AC 8 ms 3708 KiB
test_0048.txt AC 8 ms 3704 KiB
test_0049.txt AC 8 ms 3828 KiB
test_0050.txt AC 8 ms 3680 KiB
test_0051.txt AC 8 ms 3620 KiB
test_0052.txt AC 8 ms 3696 KiB
test_0053.txt AC 9 ms 3708 KiB
test_0054.txt AC 9 ms 3568 KiB
test_0055.txt AC 9 ms 3708 KiB
test_0056.txt AC 8 ms 3712 KiB
test_0057.txt AC 9 ms 3624 KiB
test_0058.txt AC 9 ms 3632 KiB
test_0059.txt AC 9 ms 3696 KiB
test_0060.txt AC 8 ms 3708 KiB
test_0061.txt AC 8 ms 3684 KiB
test_0062.txt AC 8 ms 3600 KiB
test_0063.txt AC 9 ms 3636 KiB
test_0064.txt AC 9 ms 3708 KiB
test_0065.txt AC 8 ms 3760 KiB
test_0066.txt AC 9 ms 3712 KiB
test_0067.txt AC 10 ms 3680 KiB
test_0068.txt AC 8 ms 3696 KiB
test_0069.txt AC 7 ms 3628 KiB
test_0070.txt AC 9 ms 3628 KiB
test_0071.txt AC 9 ms 3760 KiB
test_0072.txt AC 8 ms 3628 KiB
test_0073.txt AC 10 ms 3712 KiB
test_0074.txt AC 8 ms 3712 KiB
test_0075.txt AC 9 ms 3708 KiB
test_0076.txt AC 8 ms 3772 KiB
test_0077.txt AC 8 ms 3632 KiB
test_0078.txt AC 8 ms 3680 KiB
test_0079.txt AC 9 ms 3628 KiB
test_0080.txt AC 9 ms 3624 KiB
test_0081.txt AC 8 ms 3568 KiB
test_0082.txt AC 9 ms 3696 KiB
test_0083.txt AC 9 ms 3704 KiB
test_0084.txt AC 9 ms 3628 KiB
test_0085.txt AC 9 ms 3596 KiB
test_0086.txt AC 8 ms 3712 KiB
test_0087.txt AC 8 ms 3564 KiB
test_0088.txt AC 9 ms 3628 KiB
test_0089.txt AC 10 ms 3700 KiB
test_0090.txt AC 9 ms 3828 KiB
test_0091.txt AC 9 ms 3764 KiB
test_0092.txt AC 9 ms 3640 KiB
test_0093.txt AC 8 ms 3628 KiB
test_0094.txt AC 9 ms 3756 KiB
test_0095.txt AC 8 ms 3524 KiB
test_0096.txt AC 8 ms 3704 KiB
test_0097.txt AC 8 ms 3712 KiB
test_0098.txt AC 9 ms 3692 KiB
test_0099.txt AC 8 ms 3708 KiB
test_0100.txt AC 8 ms 3600 KiB
test_0101.txt AC 9 ms 3828 KiB
test_0102.txt AC 9 ms 3704 KiB
test_0103.txt AC 9 ms 3564 KiB
test_0104.txt AC 8 ms 3708 KiB
test_0105.txt AC 8 ms 3708 KiB
test_0106.txt AC 8 ms 3692 KiB
test_0107.txt AC 9 ms 3832 KiB
test_0108.txt AC 8 ms 3564 KiB
test_0109.txt AC 8 ms 3640 KiB
test_0110.txt AC 9 ms 3700 KiB
test_0111.txt AC 11 ms 3776 KiB
test_0112.txt AC 10 ms 3680 KiB
test_0113.txt AC 8 ms 3712 KiB
test_0114.txt AC 9 ms 3644 KiB
test_0115.txt AC 8 ms 3704 KiB
test_0116.txt AC 9 ms 3528 KiB
test_0117.txt AC 8 ms 3600 KiB
test_0118.txt AC 8 ms 3700 KiB
test_0119.txt AC 8 ms 3708 KiB
test_0120.txt AC 9 ms 3708 KiB
test_0121.txt AC 9 ms 3712 KiB
test_0122.txt AC 8 ms 3692 KiB
test_0123.txt AC 9 ms 3652 KiB
test_0124.txt AC 8 ms 3604 KiB
test_0125.txt AC 9 ms 3708 KiB
test_0126.txt AC 8 ms 3708 KiB
test_0127.txt AC 9 ms 3832 KiB
test_0128.txt AC 8 ms 3764 KiB
test_0129.txt AC 8 ms 3712 KiB
test_0130.txt AC 8 ms 3828 KiB
test_0131.txt AC 7 ms 3640 KiB
test_0132.txt AC 8 ms 3636 KiB
test_0133.txt AC 8 ms 3708 KiB
test_0134.txt AC 9 ms 3696 KiB
test_0135.txt AC 9 ms 3708 KiB
test_0136.txt AC 9 ms 3708 KiB
test_0137.txt AC 8 ms 3700 KiB
test_0138.txt AC 9 ms 3620 KiB
test_0139.txt AC 8 ms 3628 KiB
test_0140.txt AC 9 ms 3692 KiB
test_0141.txt AC 8 ms 3768 KiB
test_0142.txt AC 8 ms 3568 KiB
test_0143.txt AC 11 ms 3632 KiB
test_0144.txt AC 9 ms 3696 KiB
test_0145.txt AC 9 ms 3708 KiB
test_0146.txt AC 8 ms 3708 KiB
test_0147.txt AC 8 ms 3692 KiB
test_0148.txt AC 8 ms 3712 KiB
test_0149.txt AC 8 ms 3692 KiB