CodeIQ:MISSION:バレンタインを阻止せよ!会いたくても会えないスケジュール表

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「MISSION:バレンタインを阻止せよ!会いたくても会えないスケジュール表」(CodeIQ)を参照してください。

問題の概要

問題を引用します。

【問題】
それぞれのスケジュールがカンマ区切りで渡されます。(名前,日付,開始時刻,終了時刻)
平日の09:00~18:00までしか業務時間がないため、これ以外の時間帯や土日祝日は会議出席依頼を出すことはできません。
昼休み(12:00~13:00)も会議はできないので、その時間帯の場合はいったん会議を打ち切ることにします。(この時間帯をまたぐ場合は再度会議出席依頼が必要)
ゆかちぃは「X」、Y氏は「Y」とあらわします。
2016/02/01~2016/02/14までの予定が与えられたとき、会議出席依頼を出す回数を標準出力に出力してください。
なお、同時間帯にスケジュールが重複しているような入力はないものとします。

私のプログラム

Javaで解答しています。

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

public class Main{
	private static final int DAYS=14;
	private static final int MINS=60*9;
	private static final Set<Integer> HOLYDAYS;	// 2/1は0

	static {
		HOLYDAYS = new HashSet<>();
		HOLYDAYS.add(5);
		HOLYDAYS.add(6);
		HOLYDAYS.add(10);
		HOLYDAYS.add(12);
		HOLYDAYS.add(13);

	}

	int[][] X;
	int[][] Y;

	public Main(){
		X = new int[DAYS][];
		Y = new int[DAYS][];
		for(int i=0; i<DAYS; i++){
			X[i]=new int[MINS];
			Y[i]=new int[MINS];

			// 土日祝日
			if(HOLYDAYS.contains(i)){
				Arrays.fill(X[i], 1);
				Arrays.fill(Y[i], 1);
			}
			// 昼休みは会議中扱い
			else{
				Arrays.fill(X[i], 180, 240, 1);
				Arrays.fill(Y[i], 180, 240, 1);
			}
		}
	}

	public int getTimePos(int h, int m){
		h = h-9;
		return h*60 + m;
	}

	public int getTimePos(String t){
		String[] tm = t.trim().split(":");
		return getTimePos(Integer.parseInt(tm[0]), Integer.parseInt(tm[1]));
	}

	public void setSchedule(String who, int d, String st, String ed){
		int[][] target;

		if(who.equals("X")){
			target = X;
		}
		else{
			target = Y;
		}

		d = d - 1;
		int s = getTimePos(st);
		int e = getTimePos(ed);

		Arrays.fill(target[d], s, e, 1);
	}

	public void setSchedule(String input){
		String[] p = input.trim().split(",");

		String[] day = p[1].split("/");
		int d = Integer.parseInt(day[2]);

		setSchedule(p[0], d, p[2], p[3]);
	}

	public int check(){
		boolean ok = true;
		int cnt = 0;

		for(int i=0; i<DAYS; i++){
			ok = true;
			for(int j=0; j<MINS; j++){
				if((X[i][j] == 0) && (Y[i][j] == 0)){
					if(ok){
						ok = false;
						cnt += 1;
					}
				}
				else{
					ok = true;
				}
			}
		}
		return cnt;
	}

	public static void main(String[] args) throws IOException{
		try(BufferedReader br = new BufferedReader(new InputStreamReader(System.in))){
			String line;
			Main s = new Main();

			for(int i=0; (line = br.readLine()) != null; i++){
				line = line.trim();
				s.setSchedule(line);
			}

			System.out.println(s.check());
		}

	}
}

解説

この問題はアルゴリズム的に難しいところはないと思います。
単に、X、Yそれぞれのスケジュールが埋まっている部分をチェックし、両方が空いている部分を抽出するだけです。

データ構造

素朴に2次元配列を2つ用意してXとYそれぞれに割り当てています。配列は日数-時間を添字に取ります。日数は2/1が要素番号0、時間は9:00が要素番号0になります。さすがに領域が無駄なので就業時間中以外は割愛しています。ただし、時間の計算が面倒なことになるので休日は削除していません。
ここで注意すべきは何分単位にするかです。私は指定がなかったので1分単位にしています。上の問題文の引用には記述していませんが、入力される時間は分までなのでこれが最小の粒度になります。実際のテストケースでは(記憶が正しければ)5分が最小粒度でした。

スケジュール配列の初期化

コンストラクタで初期化しています。昼休みと休日を最初から予定ありにしておくのがテクニックといえばテクニックです。こうすることで比較時に休みのことを機にする必要がなくなります。

スケジュールの設定

setSchedule()で設定しています。本体はsetSchedule(String who, int d, String st, String ed)でsetSchedule(String input)はそのラッパメソッドです。このようなオーバーロードは仕事で使うプログラムでは割とよく使うと思います。
この中で使っているgetTimePos()は9:00を0としてそこから何分を返す関数です。これによって配列の要素番号を求めます。この関数もオーバーロードになっています。

スケジュール比較

check()でスケジュールを比較し、答えを求めています。
スケジュールがX、Y共に1→0になった時にカウントし、フラグを立てて0が続く間無視します。スケジュールが0→1になったらフラグをリセットします。
少し気をつけなければならないのは就業外時間を省いていることです。そのため1日単位でフラグをリセットしなければなりません。

雑感

解答してから気づいたのですが、配列は1つにしておけばよかったと思います。どうせ結果を重ね合わせるので1つにまとめておけばX、Yの両方が0であることをチェックする必要がありませんでした。