ACM/ICPC 2008 国内予選

問題A 『等しい合計点』

無難に全探索。

import java.io.*;
import java.util.*;

public class ProblemA {
    
    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(new File("A.in"));
            
            while (true) {
                int n = in.nextInt();
                int m = in.nextInt();
                
                // 終了条件
                if (n == 0 && m == 0)
                    break;
                
                // 持っているカードの管理
                int[] tarou = new int[n];
                int[] hanako = new int[m];
                
                // 合計点を格納
                int sumTarou = 0;
                int sumHanako = 0;
                
                // 入力の読み取りと合計点の計算
                for (int i = 0; i < n; i++) {
                    tarou[i] = in.nextInt();
                    sumTarou += tarou[i];
                }
                for (int i = 0; i < m; i++) {
                    hanako[i] = in.nextInt();
                    sumHanako += hanako[i];
                }
                
                // 持っているカードをソートしておく
                Arrays.sort(tarou);
                Arrays.sort(hanako);
                
                // 交換するカードの点数の和の最小値を格納
                int min = 10000;
                
                // 交換するカードを記録
                int tarouAns = 0, hanakoAns = 0;
                
                // 題意の交換方法が存在すればtrue
                boolean ok = false;
                
                // 全探索
                for (int i = 0; i < n; i++) {
                    for (int j = 0; j < m; j++) {
                        // 交換してみる
                        sumTarou = sumTarou - tarou[i] + hanako[j];
                        sumHanako = sumHanako - hanako[j] + tarou[i];
                        
                        // 合計点が一緒だったら
                        if (sumTarou == sumHanako) {
                            // 交換するカードの点数の和が最小だったら
                            if (min > tarou[i] + hanako[j]) {
                                min = tarou[i] + hanako[j];
                                tarouAns = tarou[i];
                                hanakoAns = hanako[j];
                                ok = true;
                            }
                        }
                        
                        // 交換したのを元に戻す
                        sumTarou = sumTarou + tarou[i] - hanako[j];
                        sumHanako = sumHanako + hanako[j] - tarou[i];
                    }
                }
                
                // 結果の出力
                if (ok)
                    System.out.println(tarouAns + " " + hanakoAns);
                else
                    System.out.println("-1");
            }
            
        } catch(Exception e) {
            System.err.println(e);
        }
    }

}


問題B 『月曜土曜素因数』

まずは問題文をしっかり読む。

月曜土曜数を順番に生成していって、入力数の約数かどうかを確認。

約数なら、さらに素因数であるかどうかを確認すればOK。

素因数の確認は、それまでの月曜土曜数で割り切れるかどうかを確認すれば良い。

import java.util.*;
import java.io.*;

public class ProblemB {

    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(new File("B.in"));
            
            while(true) {
                int num = in.nextInt();
                
                // 終了条件
                if (num == 1)
                    break;
                
                int monNum = 0, satNum = 0;
                int[] primeAns = new int[10000];
                int pt = 0;
                
                // 6が月曜土曜素因数かどうか
                if (num % 6 == 0) {
                    primeAns[pt] = 6;
                    pt++;
                }
    
                for (int i = 1; 7 * i < num; i++) {
                    // 月曜数の生成
                    monNum = 7 * i + 1;
                    
                    // この月曜数が入力数の因数かどうか
                    if (num % monNum == 0) {
                        // この月曜数が入力数の素因数かどうか
                        boolean thisIsPrime = true;
                        if (monNum % 6 == 0)
                            thisIsPrime = false;
                        for (int j = 1; j < i; j++) {
                            if (monNum % (7 * j + 1) == 0 || monNum % (7 * j + 6) == 0) {
                                thisIsPrime = false;
                                break;
                            }
                        }
                        // 素因数なら記録
                        if (thisIsPrime) {
                            primeAns[pt] = monNum;
                            pt++;
                        }
                    }
                    
                    // 土曜数の生成
                    satNum = 7 * i + 6;
                    
                    // この土曜数が入力数の因数かどうか
                    if (num % satNum == 0) {
                        // この土曜数が入力数の素因数かどうか
                        boolean thisIsPrime = true;
                        if (satNum % 6 == 0)
                            thisIsPrime = false;
                        for (int j = 1; j < i; j++) {
                            if (satNum % (7 * j + 1) == 0 || satNum % (7 * j + 6) == 0) {
                                thisIsPrime = false;
                                break;
                            }
                        }
                        // 素因数なら記録
                        if (thisIsPrime) {
                            primeAns[pt] = satNum;
                            pt++;
                        }
                    }
                }
                
                // 結果の出力
                System.out.print(num + ":");
                if (pt == 0)
                    System.out.println(" " + num);
                else {
                    for (int i = 0; i < pt; i++)
                        System.out.print(" " + primeAns[i]);
                    System.out.println();
                }
            }
            
        } catch(Exception e) {
            System.err.println(e);
        }
        
    }

}


問題C 『如何に汝を満足せしめむ?いざ数え上げむ…』

replaceAll()を使って、順次置換していく。

import java.util.*;
import java.io.*;

public class ProblemC {

    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(new File("C.in"));
            
            while (true) {
                // 入力の読み取り
                String str = in.nextLine();
                
                // 終了条件
                if (str.equals("."))
                    break;
                
                int count = 0;
                
                for (int p = 0; p < 3; p++) {
                    for (int q = 0; q < 3; q++) {
                        for (int r = 0; r < 3; r++) {
                            String expr = str.replaceAll("P", ""+p);
                            expr = expr.replaceAll("Q", ""+q);
                            expr = expr.replaceAll("R", ""+r);
                            
                            if (analyze(expr) == 2)
                                count++;
                        }
                    }
                }
                
                System.out.println(count);
            }
            
        } catch (Exception e) {
            System.err.println(e);
        }
    }
    
    public static int analyze(String expr) {
        String start = expr;
        
        // --の消去
        expr = expr.replaceAll("--", "");
        
        // 否定の処理
        expr = expr.replaceAll("-0", "2");
        expr = expr.replaceAll("-1", "1");
        expr = expr.replaceAll("-2", "0");
        
        // 論理積の処理
        expr = expr.replaceAll("\\(0\\*0\\)", "0");
        expr = expr.replaceAll("\\(0\\*1\\)", "0");
        expr = expr.replaceAll("\\(0\\*2\\)", "0");
        expr = expr.replaceAll("\\(1\\*0\\)", "0");
        expr = expr.replaceAll("\\(1\\*1\\)", "1");
        expr = expr.replaceAll("\\(1\\*2\\)", "1");
        expr = expr.replaceAll("\\(2\\*0\\)", "0");
        expr = expr.replaceAll("\\(2\\*1\\)", "1");
        expr = expr.replaceAll("\\(2\\*2\\)", "2");
        
        // 論理和の処理
        expr = expr.replaceAll("\\(0\\+0\\)", "0");
        expr = expr.replaceAll("\\(0\\+1\\)", "1");
        expr = expr.replaceAll("\\(0\\+2\\)", "2");
        expr = expr.replaceAll("\\(1\\+0\\)", "1");
        expr = expr.replaceAll("\\(1\\+1\\)", "1");
        expr = expr.replaceAll("\\(1\\+2\\)", "2");
        expr = expr.replaceAll("\\(2\\+0\\)", "2");
        expr = expr.replaceAll("\\(2\\+1\\)", "2");
        expr = expr.replaceAll("\\(2\\+2\\)", "2");
        
        if (expr.equals(start))
            return Integer.parseInt(expr);
        else
            return analyze(expr);
    }

}


問題D 『ちょろちょろロボット』

ロボットの向き毎にノード(4個)を作成し、dijkstraにかけるだけ。

import java.util.*;
import java.io.*;

public class problemD {

    public static void main(String[] args) {
        try {
            Scanner in = new Scanner(new File("D.in"));

            while(true) {
                int w = in.nextInt();
                int h = in.nextInt();
                
                // 終了条件
                if (w == 0 && h == 0)
                    break;
                
                // mapの生成
                Node[][] north = new Node[h][w];
                Node[][] east = new Node[h][w];
                Node[][] south = new Node[h][w];
                Node[][] west = new Node[h][w];
                
                // 命令表
                int[][] com = new int[h][w];
                
                // 経路が見つかっていないノードの集合U(自然順序付け)
                Queue groupU = new PriorityQueue();
                
                // mapの初期化
                for (int i = 0; i < h; i++) {
                    for (int j = 0; j < w; j++) {
                        // 命令表の作成
                        com[i][j] = in.nextInt();
                        
                        // ノードの作成
                        north[i][j] = new Node(""+i+"-"+j+"u");
                        east[i][j] = new Node(""+i+"-"+j+"r");
                        south[i][j] = new Node(""+i+"-"+j+"d");
                        west[i][j] = new Node(""+i+"-"+j+"l");
                        
                        // 集合Uに格納する
                        groupU.add(north[i][j]);
                        groupU.add(east[i][j]);
                        groupU.add(south[i][j]);
                        groupU.add(west[i][j]);
                    }
                }
                
                // コストの読み取り
                int c0 = in.nextInt();
                int c1 = in.nextInt();
                int c2 = in.nextInt();
                int c3 = in.nextInt();
            
                // エッジを加える
                for (int i = 0; i < h; i++) {
                    for (int j = 0; j < w; j++) {
                        int straight = c0;
                        int right = c1;
                        int reverse = c2;
                        int left = c3;
                        
                        // 命令ごとにコストを設定
                        if (com[i][j] == 0)
                            straight = 0;
                        else if (com[i][j] == 1)
                            right = 0;
                        else if (com[i][j] == 2)
                            reverse = 0;
                        else if (com[i][j] == 3)
                            left = 0;
                        
                        if (i - 1 >= 0) {
                            north[i][j].addEdge(north[i - 1][j], straight);
                            west[i][j].addEdge(north[i - 1][j], right);
                            south[i][j].addEdge(north[i - 1][j], reverse);
                            east[i][j].addEdge(north[i - 1][j], left);
                        }
                        
                        if (j + 1 < w) {
                            east[i][j].addEdge(east[i][j + 1], straight);
                            north[i][j].addEdge(east[i][j + 1], right);
                            west[i][j].addEdge(east[i][j + 1], reverse);
                            south[i][j].addEdge(east[i][j + 1], left);
                        }
                        
                        if (i + 1 < h) {
                            south[i][j].addEdge(south[i + 1][j], straight);
                            east[i][j].addEdge(south[i + 1][j], right);
                            north[i][j].addEdge(south[i + 1][j], reverse);
                            west[i][j].addEdge(south[i + 1][j], left);
                        }
                        
                        if (j - 1 >= 0) {
                            west[i][j].addEdge(west[i][j - 1], straight);
                            south[i][j].addEdge(west[i][j - 1], right);
                            north[i][j].addEdge(west[i][j - 1], left);
                            east[i][j].addEdge(west[i][j - 1], reverse);
                        }
                    }
                }
                
                List groupV = dijkstra(groupU, east[0][0]);
                        
                // ゴールへの入り方は東向きか南向きのみ
                Node goalEast = (Node) groupV.get(groupV.indexOf(east[h - 1][w - 1]));
                Node goalSouth = (Node) groupV.get(groupV.indexOf(south[h - 1][w - 1]));
                
                System.out.println(Math.min(goalEast.distance, goalSouth.distance));
            }
        } catch(Exception e) {
            System.err.println(e);
        }    
    }
    
    public static List dijkstra(Queue groupU, Node start) {
        // 最短経路が確定している集合V
        List groupV = new ArrayList();
        
        // 出発点の距離を0にする
        start.distance = 0;
        start.routeFound = true;
        
        // startノードをgroupUの先頭に持ってくる
        groupU.remove(start);
        groupU.add(start);
        
        while(groupU.size() > 0) {
            // 集合Uから最も距離の小さいノードを取り出す
            Node st = (Node) groupU.poll();
            
            // これ以上先に進めない(連結グラフでない)
            if (!st.routeFound)
                break;
            
            // stノードを集合Vに追加する
            st.inV = true;
            groupV.add(st);
            
            for (int i = 0; i < st.arrows.size(); i++) {
                Edge arrow = (Edge) st.arrows.get(i);
                Node target = arrow.target;
                int weight = arrow.weight;
                
                if (!target.inV) {
                    if (!target.routeFound || target.distance > (st.distance + weight)) {
                        
                        // 最短経路の更新
                        target.distance = st.distance + weight;
                        target.routeFound = true;
                        
                        // groupUを最短経路順にソートしておく
                        groupU.remove(target);
                        groupU.add(target);
                        }
                }
            }
            
        }
        
        return groupV;
    }

}

class Node implements Comparable {
    String name;    // ノード名
    int distance;    // 現時点での最短経路の距離
    List arrows;    // ノードから出ているエッジのリスト
    boolean routeFound;    // 最短経路が見つかっているか
    boolean inV;    // 最短路が見つかっていれば集合Vに含まれる
    
    // コンストラクタ
    public Node(String name) {
        this.name = name;
        this.distance = Integer.MAX_VALUE;
        this.arrows = new ArrayList();
        this.routeFound = false;
        this.inV = false;
    }
    
    // このノードを出発点とするエッジを追加
    public void addEdge(Node target, int weight) {
        arrows.add(new Edge(target, weight));
    }

    // distanceに応じてソートする
    public int compareTo(Object o) {
        return this.distance - ((Node) o).distance;
    }
}

class Edge {
    Node target;
    int weight;
    
    // コンストラクタ
    public Edge(Node target, int weight) {
        this.target = target;
        this.weight = weight;
    }
}