結果

問題 No.5025 Forest Fire
コンテスト
ユーザー 37zigen
提出日時 2026-10-24 17:07:55
言語 Java
(openjdk 27 + ACL)
コンパイル:
javac -J-Duser.language=en -encoding UTF8 -cp /opt/aclib/ac_library.jar _filename_
実行:
java -ea -Xmx700m -Xss256M -DONLINE_JUDGE=true -cp .:/opt/aclib/ac_library.jar _class_
結果
AC  
実行時間 1,949 ms / 2,000 ms
+ 820µs
コード長 12,726 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 2,119 ms
コンパイル使用メモリ 96,360 KB
実行使用メモリ 57,872 KB
スコア 175,287,075
最終ジャッジ日時 2026-10-24 17:10:42
合計ジャッジ時間 161,544 ms
ジャッジサーバーID
(参考情報)
judge2_0 / <nil>
純コード判定しない問題か言語
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
other AC * 50
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.Random;

public class Main {
    static final int SAMPLE_COUNT = 10;
    static final int MAX_SEGMENT_LENGTH = 20;
    static final long TIME_LIMIT_NS = 1_850_000_000L;

    static int n, k, cells;
    static int[] d;
    static boolean[][] dry;
    static final Random random = new Random();

    static final class FastScanner {
        private final BufferedInputStream in = new BufferedInputStream(System.in);

        String next() throws IOException {
            StringBuilder sb = new StringBuilder();
            int c;
            do {
                c = in.read();
            } while (c <= ' ' && c != -1);
            while (c > ' ') {
                sb.append((char) c);
                c = in.read();
            }
            return sb.toString();
        }

        int nextInt() throws IOException {
            return Integer.parseInt(next());
        }
    }

    static void generateDrySamples() {
        dry = new boolean[SAMPLE_COUNT][cells];
        for (int s = 0; s < SAMPLE_COUNT; s++) {
            for (int v = 0; v < cells; v++) {
                dry[s][v] = random.nextInt(10) < d[v];
            }
        }
    }

    // A path is indexed by its forward coordinate: column for a horizontal
    // path and row for a vertical path. The other coordinate may change by
    // -1, 0, or +1 at each step.
    static int transverse(int vertex, boolean horizontal) {
        return horizontal ? vertex / n : vertex % n;
    }

    static int vertexAt(int index, int transverse, boolean horizontal) {
        return horizontal ? transverse * n + index : index * n + transverse;
    }

    // Randomized DFS for the interval [left, right]. The start vertex stays
    // fixed. If right is not the last index, the destination must match the
    // old path vertex exactly; at the terminal board edge, the destination is
    // free. The distance bound prunes states that cannot reach a fixed target.
    static boolean dfsSegment(int[] path, boolean horizontal, int index, int right,
                              int coordinate, int target, boolean fixedTarget) {
        path[index] = vertexAt(index, coordinate, horizontal);
        if (index == right) {
            return !fixedTarget || coordinate == target;
        }

        int nextIndex = index + 1;
        int low = Math.max(0, coordinate - 1);
        int high = Math.min(n - 1, coordinate + 1);
        int[] choices = new int[high - low + 1];
        int count = 0;
        for (int value = low; value <= high; value++) choices[count++] = value;
        for (int i = count - 1; i > 0; i--) {
            int j = random.nextInt(i + 1);
            int temp = choices[i];
            choices[i] = choices[j];
            choices[j] = temp;
        }

        for (int nextCoordinate : choices) {
            // For a left-to-right path, |vertical difference| cannot exceed
            // the remaining horizontal distance. The vertical case is symmetric.
            if (fixedTarget && Math.abs(target - nextCoordinate) > right - nextIndex) {
                continue;
            }
            if (dfsSegment(path, horizontal, nextIndex, right, nextCoordinate,
                           target, fixedTarget)) {
                return true;
            }
        }
        return false;
    }

    static void rerandomizeSegment(int[] path, boolean horizontal, int left, int right) {
        if (left >= right) return;
        int startCoordinate = transverse(path[left], horizontal);
        boolean fixedTarget = right < n - 1;
        int target = transverse(path[right], horizontal);
        // When the destination is internal it is fixed exactly. When it is
        // on the far edge, any coordinate on that edge is accepted.
        if (!dfsSegment(path, horizontal, left, right, startCoordinate, target, fixedTarget)) {
            // The old segment always provides a feasible path, so this is only
            // a defensive fallback if the DFS implementation is changed later.
            return;
        }
    }

    // Reverse-direction DFS: start at the tail-side vertex and move toward
    // index 0. The index-0 endpoint is free; otherwise the left endpoint must
    // connect exactly to the unchanged part of the path.
    static boolean dfsSegmentReverse(int[] path, boolean horizontal, int index, int left,
                                     int coordinate, int target, boolean fixedTarget) {
        path[index] = vertexAt(index, coordinate, horizontal);
        if (index == left) {
            return !fixedTarget || coordinate == target;
        }

        int nextIndex = index - 1;
        int low = Math.max(0, coordinate - 1);
        int high = Math.min(n - 1, coordinate + 1);
        int[] choices = new int[high - low + 1];
        int count = 0;
        for (int value = low; value <= high; value++) choices[count++] = value;
        for (int i = count - 1; i > 0; i--) {
            int j = random.nextInt(i + 1);
            int temp = choices[i];
            choices[i] = choices[j];
            choices[j] = temp;
        }

        for (int nextCoordinate : choices) {
            // Remaining steps are nextIndex - left, in reverse direction.
            if (fixedTarget && Math.abs(target - nextCoordinate) > nextIndex - left) {
                continue;
            }
            if (dfsSegmentReverse(path, horizontal, nextIndex, left, nextCoordinate,
                                  target, fixedTarget)) {
                return true;
            }
        }
        return false;
    }

    static void rerandomizeSegmentReverse(int[] path, boolean horizontal, int left, int right) {
        if (left >= right) return;
        int startCoordinate = transverse(path[right], horizontal);
        boolean fixedTarget = left > 0;
        int target = transverse(path[left], horizontal);
        if (!dfsSegmentReverse(path, horizontal, right, left, startCoordinate,
                               target, fixedTarget)) {
            // The old segment is always a feasible fallback.
            return;
        }
    }

    // Sum of squared dry-component sizes over the 10 shared samples.
    // The common factor 1 / (SAMPLE_COUNT * N * N) is omitted for comparison.
    static long evaluate(int cutStamp, int[] cutMark, int[] seen, int[] queue,
                         int[] visitStamp) {
        long total = 0;
        for (int s = 0; s < SAMPLE_COUNT; s++) {
            int mark = ++visitStamp[0];
            for (int v = 0; v < cells; v++) {
                if (!dry[s][v] || cutMark[v] == cutStamp || seen[v] == mark) continue;

                int head = 0;
                int tail = 0;
                queue[tail++] = v;
                seen[v] = mark;
                while (head < tail) {
                    int u = queue[head++];
                    int row = u / n;
                    int col = u - row * n;
                    int w;
                    if (row > 0) {
                        w = u - n;
                        if (dry[s][w] && cutMark[w] != cutStamp && seen[w] != mark) {
                            seen[w] = mark;
                            queue[tail++] = w;
                        }
                    }
                    if (row + 1 < n) {
                        w = u + n;
                        if (dry[s][w] && cutMark[w] != cutStamp && seen[w] != mark) {
                            seen[w] = mark;
                            queue[tail++] = w;
                        }
                    }
                    if (col > 0) {
                        w = u - 1;
                        if (dry[s][w] && cutMark[w] != cutStamp && seen[w] != mark) {
                            seen[w] = mark;
                            queue[tail++] = w;
                        }
                    }
                    if (col + 1 < n) {
                        w = u + 1;
                        if (dry[s][w] && cutMark[w] != cutStamp && seen[w] != mark) {
                            seen[w] = mark;
                            queue[tail++] = w;
                        }
                    }
                }
                total += (long) tail * tail;
            }
        }
        return total;
    }

    static int markCuts(int[] path1, int[] path2, int stamp, int[] cutMark) {
        for (int v : path1) cutMark[v] = stamp;
        for (int v : path2) cutMark[v] = stamp;
        int cutCount = 0;
        for (int v = 0; v < cells; v++) {
            if (d[v] > 0 && cutMark[v] == stamp) cutCount++;
        }
        return cutCount;
    }

    public static void main(String[] args) throws IOException {
        long startTime = System.nanoTime();
        FastScanner fs = new FastScanner();
        n = fs.nextInt();
        k = fs.nextInt();
        cells = n * n;
        d = new int[cells];
        for (int r = 0; r < n; r++) {
            String row = fs.next();
            for (int c = 0; c < n; c++) {
                d[r * n + c] = row.charAt(c) - '0';
            }
        }
        generateDrySamples();

        // Initial solution: a plus-shaped cross through the center row/column.
        int[] path1 = new int[n]; // horizontal path
        int[] path2 = new int[n]; // vertical path
        int middle = n / 2;
        for (int i = 0; i < n; i++) {
            path1[i] = middle * n + i;
            path2[i] = i * n + middle;
        }
        int[] candidate1 = new int[n];
        int[] candidate2 = new int[n];
        int[] cutMark = new int[cells];
        int[] seen = new int[cells];
        int[] queue = new int[cells];
        int[] visitStamp = {0};
        boolean[] bestCut = new boolean[cells];
        int cutStamp = 1;

        int initialCutCount = markCuts(path1, path2, cutStamp, cutMark);
        long currentValue = evaluate(cutStamp, cutMark, seen, queue, visitStamp);
        long bestValue = currentValue;
        for (int v = 0; v < cells; v++) {
            bestCut[v] = d[v] > 0 && cutMark[v] == cutStamp;
        }

        long deadline = startTime + TIME_LIMIT_NS;
        long trials = 0, withinK = 0, evaluated = 1, improvements = 0;
        int maxLength = Math.min(MAX_SEGMENT_LENGTH, n - 1);

        // Hill climbing: mutate one random segment of one of the two paths.
        while (maxLength >= 2 && System.nanoTime() < deadline) {
            trials++;
            System.arraycopy(path1, 0, candidate1, 0, n);
            System.arraycopy(path2, 0, candidate2, 0, n);

            int anchor = random.nextInt(n); // randomly selected path vertex
            int length = 1 + random.nextInt(maxLength); // at most 20 steps
            boolean horizontal = random.nextBoolean();
            boolean reverse = random.nextBoolean();
            int left, right;
            if (!reverse) {
                left = anchor;
                right = Math.min(n - 1, left + length); // clamp at the tail-side end
            } else {
                right = anchor;
                left = Math.max(0, right - length); // clamp at the head-side end
            }
            int[] candidatePath = horizontal ? candidate1 : candidate2;
            if (reverse) {
                rerandomizeSegmentReverse(candidatePath, horizontal, left, right);
            } else {
                rerandomizeSegment(candidatePath, horizontal, left, right);
            }

            int stamp = ++cutStamp;
            int cutCount = markCuts(candidate1, candidate2, stamp, cutMark);
            if (cutCount > k) continue;
            withinK++;

            long value = evaluate(stamp, cutMark, seen, queue, visitStamp);
            evaluated++;
            if (value < currentValue) {
                improvements++;
                currentValue = value;
                System.arraycopy(candidate1, 0, path1, 0, n);
                System.arraycopy(candidate2, 0, path2, 0, n);
                bestValue = value;
                for (int v = 0; v < cells; v++) {
                    bestCut[v] = d[v] > 0 && cutMark[v] == stamp;
                }
            }
        }

        int answerCount = 0;
        for (boolean cut : bestCut) if (cut) answerCount++;
        StringBuilder out = new StringBuilder();
        out.append(answerCount).append('\n');
        for (int v = 0; v < cells; v++) {
            if (bestCut[v]) out.append(v / n).append(' ').append(v % n).append('\n');
        }
        System.out.print(out);
        System.err.printf("initialCutCount=%d trials=%d withinK=%d evaluated=%d improvements=%d bestValue=%d elapsed=%.3fms%n",
                initialCutCount, trials, withinK, evaluated, improvements, bestValue,
                (System.nanoTime() - startTime) / 1e6);
    }
}
0