Saturday, June 16, 2007

Final Indonesia National Contest 2007 :: Problem F - Jakarta Traffic Jam

ACM/ICPC Indonesia National Contest 2007

Problem F

Jakarta Traffic Jam

Time Limit: 1s


The traffic in Jakarta is terrible. Streets in Jakarta may get congested with traffic jam during rush hour. This means it will take longer time to pass through those streets since you can only drive half your speed during rush hours. Therefore, you need to plan your trip carefully if you don't want to be late for your activities.

For example, consider a street from P to Q which will need 20 minutes to drive in a normal time. The street is congested with traffic at 15:00 until 16:00. Now, let's consider some sample conditions that might happen related to rush hours:
  1. If you start your trip 15 minutes before the rush hour (start from P at 14:45), then when the traffic starts to get congested, you can only drive half of your normal speed, thus, you will need 10 (2x5) more minutes to reach Q. Thus, the total time you need to go from P to Q is 15 + 10 = 25 minutes.

  2. If you start your journey at the last 5 minutes of the rush hour (start from P at 15:55), the distance you have reached in the first 5 minutes is equal to the distance you can reach in 2.5 minutes during normal hours. You will need 17.5 more minutes to get to Q. Thus, the total time you need to go from P to Q is 5 + 17.5 = 22.5 minutes.

  3. Other combinations of (a) and (b).
Given a map which has at most N intersections and M streets, find out how many minute(s) you need to go from s to d. You want to reach d as soon as possible without wasting any seconds or microseconds, or even nanoseconds.

Input

The input contains several test cases. Each case begins with two integers N (1<= N <=20) and M. Each intersection will be numbered from 0 to N-1.

The first three integers P, Q, and T (1<= T <=50) in each of the following M line means there is a street connecting intersection P to intersection Q which need T minutes driving in normal time. These integers will be followed by either character 'N' or 'R'. 'N' will not be followed by any characters and it means the street has no rush hours. 'R' will be followed by the starting and ending time of the rush hour in the format of hh:mm (00:00 to 23:59). No rush hours will rollover past midnight.

The last line of each case contains s - your starting point, d - your destination and w - current time.

Input is terminated by a line containing two zeroes. This line should not be processed.

Output

For each case, output a line containing the minimum time you need to go from s to d in minutes. Your output should always contain two digits after the decimal point.

Sample Input

2 1
0 1 20 R 15:00 16:00
0 1 14:45
3 3
0 1 20 R 15:00 16:00
1 3 10 N
2 1 35 R 16:30 17:00
0 2 15:55
0 0

Sample Output

25.00
72.50


Problem Setter: Danny Gunawan


Pembahasan Problem F - Jakarta Traffic Jam

Soal ini secara algoritma lebih mudah daripada soal Taxi di babak penyisihan, tetapi bobotnya dipindahkan ke matematika!

Ada N persimpangan dan M jalan. Setiap jalan mempunyai jam kemacetan dimana kalau anda melalui jalan tersebut saat macet maka kecepatan anda akan menurun setengah kecepatan awal. Pertanyaannya, berapakah waktu tercepat dari s ke d?

Anda tidak perlu A* seperti soal Taxi di penyisihan, algoritma umum Dijkstra bisa diterapkan untuk memecahkan soal ini karena anda hanya perlu meminimalisasi waktu dari source ke destination. Tepat seperti apa yang Dijkstra lakukan. Bagaimanapun juga, A* adalah versi superior dan kode nya pun tidak jauh beda dengan Dijkstra dan mungkin lebih gampang di implementasi. Untuk belajar A*, anda bisa buka pembahasan soal penyisihan Taxi. Untuk belajar Dijkstra anda bisa lihat pembahasan soal ini.

Saya akan coba menjelaskan Dijkstra secara kasarnya saja. Untuk cara formalnya, anda bisa baca dari buku Introduction to Algorithms atau cari sendiri lewat Google :)

Cara Dijkstra bekerja adalah dengan selalu memprocess node yang memiliki waktu tercepat dan berhenti saat semua node sudah diprocess. Dalam memprocess suatu node, node tersebut akan meng-update waktu yang dibutuhkan untuk tiba di node lain dari node tersebut. Saat suatu node diprocess, maka waktu tercepat untuk sampai ke node tersebut sudah diketahui dengan pasti. Silahkan lihat dibawah untuk implementasinya.

#include <stdio.h>

// ini adalah function mematikan dari soal ini, membutuhkan sedikit if dan else + rekursi :)
double calculate(double waktuSekarang, double lamaPerjalanan, int rushStart, int rushEnd){
    double waktuTiba = waktuSekarang + lamaPerjalanan;

    if (rushStart <= waktuSekarang && waktuSekarang <= rushEnd){
        // waktu mulainya kena rush hour, case yang B

        // berapa lama lagi sampai rush hournya berhenti
        double lamanyaRushHour = rushEnd - waktuSekarang;

        if (lamanyaRushHour > lamaPerjalanan * 2){
            // sepanjang anda berjalan, macet melulu
            return lamaPerjalanan * 2;      
        } else {
            // ditengah-tengah perjalanan anda berhasil lolos dari rush hour
            double jarakTersisa = lamaPerjalanan - lamanyaRushHour/2;
            return lamanyaRushHour + jarakTersisa;
        }
    } else if (waktuSekarang <= rushStart && rushStart <= waktuTiba){
        // ditengah perjalanan anda terkena rush hour, case yang A
        
        // anda bisa jalan dengan kecepatan normal saat belum terkena rush hour
        double jalanNormal = rushStart - waktuSekarang;    
        
        // disinilah anda mulai terkena rush hour dan kalau dilihat, anda mirip Case B diatas
        // yaitu anda memulai dengan terkena rush hour! maka tinggal panggil aja calculate() sekali lagi ;)
        return jalanNormal + calculate(
            waktuSekarang + jalanNormal,    // waktu sekarang sudah bertambah sebanyak perjalanan yang ditempuh
            lamaPerjalanan - jalanNormal,   // lama perjalanan berkurang sebanyak yang ditempuh
            rushStart, rushEnd);            // waktu rush hour start dan end masih tetap sama
    } else {
        return lamaPerjalanan;              // anda mujur sekali tidak terkena rush hour
    }
}

int getTime(){
    int hh,mm;
    scanf("%d:%d",&hh,&mm);
    return hh*60 + mm;
}

int main(){
    int travel_time[20][20];     // travel_time[i][j] waktu normal berjalan dari i ke j
    int rush_start[20][20];      // rush_start[i][j] waktu mulai rush hour untuk jalan i ke j
    int rush_end[20][20];        // rush_end[i][j] waktu selesai rush hour untuk jalan i ke j
    double waktu[20];            // waktu[x] adalah waktu tercepat untuk sampai di node x
    int N,M,i,j,k,P,Q,T,s,d,pq[20],pqSize;
    char NR[2];

    while (scanf("%d %d",&N,&M)!=EOF && (N||M)){
        memset(travel_time,-1,sizeof(travel_time));
        memset(rush_start,-1,sizeof(rush_start));
        memset(rush_end,-1,sizeof(rush_end));

        for (i=0; i<M; i++){
            scanf("%d %d %d %s",&P,&Q,&T,NR);
            travel_time[P][Q] = travel_time[Q][P] = T;
            if (NR[0]=='R'){
                rush_start[P][Q] = rush_start[Q][P] = getTime();
                rush_end[P][Q] = rush_end[Q][P] = getTime();
            }
        }
        scanf("%d %d",&s,&d);

        for (i=0; i<N; i++){
            pq[i] = i;           // masukkan semua node ke Priority Queue
            waktu[i] = 1e10;     // waktu di semua node adalah tak terhingga
        }
        waktu[s] = getTime();    // set waktu dari starting intersection s
        
        // Dijkstra, process berdasarkan waktu yang paling pagi
        for (pqSize=N; pqSize>0; pqSize--){

            // cari node terpagi yang belum diprocess
            k = 0;
            for (i=1; i<pqSize; i++)
                if (waktu[pq[i]] < waktu[pq[k]])
                    k = i; // update k kalau ketemu node yang lebih pagi

            i = pq[k];               // pq[k] adalah node dengan waktu terpagi, simpan di i
            pq[k] = pq[pqSize-1];    // buang node k dari priority queue

            // process node i (node yang terpagi saat ini)
            for (j=0; j<N; j++){
                if (travel_time[i][j]!=-1){
                    double duration = travel_time[i][j];
                    
                    if (rush_start[i][j]!=-1){
                        duration = calculate(    // hitung durasi waktu yang diperlukan dari intersection i ke j
                            waktu[i],            // waktu saat ini
                            travel_time[i][j],   // waktu perjalanan normal dari i ke j
                            rush_start[i][j],    // jam mulai rush hour untuk jalan i ke j
                            rush_end[i][j]);     // jam selesai rush hour untuk jalan i ke j
                    }

                    // jika kita bisa travel ke j dari i lebih cepat, maka update waktu j
                    if (waktu[i] + duration < waktu[j]){
                        waktu[j] = waktu[i] + duration;
                    }
                }
            }
        }

        // waktu tercepat sampai di destination d adalah waktu di d dikurang dengan waktu di s
        printf("%.2lf\n", waktu[d] - waktu[s]);
    }
}

Kembali ke halaman utama

No comments:

Post a Comment