import java.io.*; import java.util.*; /** * 2007年7月6日開催の国内予選 B問題のプログラム例 * * ■ 問題の概要 *  ログイン、ログアウトの時間、ユーザID、ログインしたPCのIDが記載されたログファイルがある。 * そのファイルを読み込み、あるユーザがある時間帯に何分ログインしていたかを出力するプログラムを作成する。 * * ■ 解答のポイント *  プログラムが回答すべきことは、あるユーザIDの、ある時間帯におけるログインしている時間である。 * よって、PCのIDを記録する必要はない。但し、ユーザは同時に複数のPCにログインできるため、PCのIDを無視 * すると、例えばログイン、ログイン、ログアウト、ログアウト、というような複数回ログインするログとして * 解釈される。そこで、ログファイルの読み込み時はユーザ毎にログイン回数を示す変数を用意し、ログインした * ときにインクリメント、ログアウトしたときにデクリメントする。ログアウト時にその変数の値が0になっていれば * すべてのPCからログアウトしたことになる。最初にログインしてから、このすべてのPCからのログアウトするまでの * 時間をまずは記録する。 *  次に、問題の質問(ユーザIDと時間帯が指定されている)を読み込む。これは特に特別な処理はせずに、そのまま記録する。 *  そして、質問に回答するために、質問が指定する時間帯と該当するIDを持つユーザのすべてのログイン時間帯の重なり合う部分 * をすべて抜き出し、その総時間を回答する。 * *  これまでの処理を論理式で表すと、 (l1∨l2∨l3∨...)∧q1となる。ここで、l1、l2は、ユーザがあるPCへログインした時間、 * q1は質問文で指定されている時間帯である。複数のPCにログインできるため、それらの時間のORを取り、その結果と質問の時間帯の * ANDを取れば、回答の時間が算出できる。プログラム上では∨(OR)の部分は、ログイン回数のインクリメント、デクリメントで * 実装し、∧(AND)部分は、以下のように実装している。 *   Math.max(0,Math.min(l.getTimeE(),q.getTimeE())-Math.max(l.getTimeS(),q.getTimeS())); *  lは(l1∨l2∨l3∨...)、qは、q1に対応する。それぞれの時間帯の開始時間をgetTimeS()、終了時間をgetTimeE()メソッドで * 取得する。終了時間は数の少ない方、開始時間は数の大きい方を選択し、その差分を算出する。但し、重なり合う部分がない場合 * マイナスの数値になるため、結果と0との小さい方の値を求める数値とする。 */ public class Exp2007_B { /** 時間帯(開始時間、終了時間)を表すクラス */ static class Interval { int m_time_s; int m_time_e; Interval(int time_s,int time_e){ m_time_s = time_s; m_time_e = time_e; } /** 開始時間の取得 */ int getTimeS(){ return m_time_s; } /** 終了時間の取得 */ int getTimeE(){ return m_time_e; } }; /** 問題の質問を表すクラス */ static class Question { int m_user_id; Interval m_interval; Question(int user_id,int time_s,int time_e){ m_user_id = user_id; m_interval = new Interval(time_s,time_e); } /** 対象となるユーザIDの取得 */ int getUserID(){ return m_user_id; } /** 対象となる時間帯の取得 */ Interval getInterval(){ return m_interval; } }; /** 2つの時間帯の重なり合う部分の時間を取得する */ private static int intersection(Interval i1,Interval i2){ return Math.max(0,Math.min(i1.getTimeE(),i2.getTimeE())-Math.max(i1.getTimeS(),i2.getTimeS())); } /** メイン関数、この関数内ですべての処理を行う。 */ public static void main(String[] args) throws Exception { // コマンドラインから入力ファイル、出力ファイルの名前を指定する。指定がない場合、エラーを出力してプログラムは終了する。 if(args.length != 2){ System.out.println("Exp2007_B [input data filename] [output data filename]"); return; } System.out.println("Input["+args[0]+"] Output["+args[1]+"]"); // 入力ファイルを読み込む準備を行う。 BufferedReader br = new BufferedReader(new FileReader(args[0])); // プログラムの出力を一時保存しておく変数 Vector outputs = new Vector(); // この変数に入力ファイルの一行が記録される。 String line = null; while(null!=(line=br.readLine())){ // データの最初の一行(PC数 学生数)を読み込む。但し、この情報は使わない。 StringTokenizer tok = new StringTokenizer(line," "); int pc_num = Integer.parseInt(tok.nextToken()); int user_num = Integer.parseInt(tok.nextToken()); // 確認のために、PC数、学生数を表示する。 System.out.println("# pc="+pc_num+", # user="+user_num); if(pc_num == 0 && user_num == 0){ // PC数、学生数が共に0の場合、終了する。 break; } // 学生のログイン、ログオフの情報を保存する。ここでは何も処理をせず、ただ文字列として保存する。 readRec(br); // すべての質問を習得する。 Vector questions = readQuestion(br); // 質問の数分だけループする。 for(Question question : questions){ // 保存しておいた学生のログオン情報から、特定のユーザのログイン時間をすべて取得する。 Vector intervals = getInterval(question.getUserID()); // 質問の時間内のログイン時間の総計 int sum = 0; for(Interval inv : intervals){ // 質問の時間と、ログイン時間の共通部分を足し合わせる。 sum += intersection(inv,question.getInterval()); } // 結果はすぐにファイルに出力せず、可変長の配列のようなVectorクラスに保存する。 outputs.add(new Integer(sum)); } } // ファイルのクローズ br.close(); // 保存しておいたログイン時間について、ファイルに出力する。 PrintWriter pw = new PrintWriter(new FileWriter(args[1])); for(Integer value : outputs){ pw.println(value); } pw.close(); } /** ログイン、ログアウト情報を保存する変数 */ private static String[] m_records = null; /** ログイン、ログアウト情報を入力ファイルから取得する。 */ private static void readRec(BufferedReader br) throws Exception{ m_records = null; int number = Integer.parseInt(br.readLine()); m_records = new String[number]; for(int cnt=0;cnt getInterval(int user_id){ Vector intervals = new Vector(); int login_count = 0; int time_s = 0; for(int cnt=0;cnt readQuestion(BufferedReader br) throws Exception { int number = Integer.parseInt(br.readLine()); Vector questions = new Vector(); String line = null; for(int cnt=0;cnt