Monday, November 2, 2009

Fast and Effective Histogram Construction (or Sequence Segmentation)

Felix Halim, Panagiotis Karras, Roland Yap

[ CIKM09 Paper, AAAI10 Paper, Source Codes ]

Abstract

Histogram construction or sequence segmentation is a basic task with applications in database systems, information retrieval, and knowledge management. Its aim is to approximate a sequence by line segments. Unfortunately, the quadratic algorithm that derives an optimal histogram for Euclidean error lacks the desired scalability. Therefore, sophisticated approximation algorithms have been recently proposed, while several simple heuristics are used in practice. Still, these solutions fail to resolve the efficiency-quality tradeoffs in a satisfactory manner. In this paper we take a fresh view on the problem. We propose conceptually clear and scalable algorithms that efficiently derive high-quality histograms. We experimentally demonstrate that existing approximation schemes fail to deliver the desired efficiency and conventional heuristics do not fare well on the side of quality. On the other hand, our schemes match or exceed the quality of the former and the efficiency of the latter.

Background and Related Work

Given a finite data sequence D = d0, d1, ..., dn-1. Construct a histogram HB from D using at most B buckets that minimizes the total error of all the buckets. The error for a bucket is defined as the sum of squared difference between all elements in that bucket with the mean value of that bucket. This problem can be solved optimally using Dynamic Programming (DP). However, the running time of the optimal construction is quadratic O(n^2 * B). Therefore, approximations algorithms have been proposed recently to get high quality solution efficiently such as AHistL and DnS. AHistL decompose the problem into two parts. In the inner part gives the error for a parameter value is within a "good" range. The outer part searches for the appropriate range and sets the parameter. The parameters to this algorithm are delta and epsilon. The delta is adjusted over time by the outer part while the epsilon is a predetermined parameter. The smaller the epsilon, the more accurate the result (the denser the list generated in the inner part) and the slower the algorithm. The complexity of this algorithm is O(n + B3 * (e-2 + log(n)) * log(n)). AHistL is not practical when B is large and as shown in our experiments, after a certain value of B, it is better off to switch to the optimal construction algorithm. DnS (Divide and Segment) works by splitting the input sequence into X sub-partitions. Then it segments each of the X sub-partitions (into B partitions each) optimally using DP. Each sub-partition produces B partitions (samples), yielding a total of X * B samples. The optimal DP segmentation is run on the X * B samples to get the final best of B partitions. Choosing X = (n/B)2/3 yield the best complexity of O(n4/3 * B2/3).

Our Contributions

We developed an algorithm that can produce good quality (low total error) as fast as possible. We started by developing an effective and efficient greedy-based local search algorithm and used it to produce high quality samples. The samples are used to generate the final B partitions optimally using DP. We experimentally show that the number of samples collected is less than those of DnS and yet have better quality thus giving a better segmentation results and faster runtime than DnS. We also compared the time vs. quality tradeoffs with many approximation algorithms. Even though we could not give the bound for the error, we can show the efficiency and the quality of our algorithms using some well known datasets. The experiment results showed that our algorithm matches or outperforms the approximation algorithms for both quality and speed, especially for large number of elements (n) and segments (B).

Our greedy-based local search algorithm (GDY) works by moving from one solution to another. A solution is a set of partitions S containing B+1 elements: s0,s1,s2,...,sB where s0 is 0 and sB is n and s1,s2,..,s(B-1) are the "movable" partitions that separates the n elements into B buckets. GDY continuously moves one solution to another by picking one movable partition that yields to the least increase in total error and put it in between partitions that yields the most decrease in total error, until no more improving move can be performed (stuck in a local optima). The performance of the GDY algorithm matches the performance of the one dimensional variant of MHIST heuristic while giving better quality over a set of datasets that we tested. We can perform multiple runs/iterations (I) of the GDY algorithm by randomizing the initial partitions to produce high quality samples which are then used to construct the final B partitions using DP. We call this algorithm GDY_(I)DP, where I is a small constant (i.e, 4-32). With GDY_32DP manage to outperform DnS in term of runtime by an order of magnitude faster, while matching the segmentation quality of DnS. We further scale our algorithm, to cope with very large n ~ 100,000 as in the synthetic dataset, by performing the DP in batch on the samples. We create a batch for (roughly) every sqrt(n) consecutive (adjacent) samples. For each batch, we have an estimate on how many partitions P should be given to that batch. This estimate is acquired by running a GDY algorithm and count how many partitions fall into that batch range. Then we do DP on the samples in the batch to produce the same number of P partitions thus optimizing the locations of the P partitions inside the batch. The batch strategy (GDY_BDP) maintains the linear runtime complexity in terms of n and B, as well as the segmentation quality in segmenting very large data sequence.

Greedy Runs Produce Good Samples

A single GDY run produces a set of partitions (samples). Surprisingly, more than 50% of the partitions are the partitions that belong to the global optimum partitions. Running the randomized greedy algorithm multiple times can improve the percentage of samples that belong to the optimum partitions. DP is able to pick the final best B partitions out of the samples yielding a solution that is very close to the optimal. Furthermore, by modifying the DP to show the number of unique optimal solutions, we can examine the datasets we use and we discover that there is only one global optimum for most values of B. Our GDY sampling correctly samples partitions near the global optimum positions.

Play with the tool below to see the samples generated by running 32 iterations of GDY. The red vertical lines are the global optimum partitions. We also include the uniform sampling that is used by DnS for comparisons.

Datasets:   Segments:

We can see that the number of samples obtained from running 32 GDYs (the green dots) is mostly less than DnS (the red dots) and the samples are mostly very good (the samples are all close to the optimal segmentation positions). This shows that GDY produces better samples (in quality and in number of samples) than DnS. Since DnS uses uniform sampling for each X sub-partitions, it maybe the case that the first 90% of the input is a very steady sequence and the last 10% is a random sequence. In this case, if DnS splits into X = 10 partitions then the first 9*B samples from the first 9 partitions are useless (bad samples) and only the last B samples are good. Our Greedy will be most likely treat the first 90% of the sequence as a single bucket and samples more on the last 10% of the input sequence. This shows why uniform sampling can be a poor way of sampling.

Datasets

balloon      2001 x 2   http://lib.stat.cmu.edu/datasets/
darwin       1400 x 1   http://www.stat.duke.edu/~mw/ts_data_sets.html
shuttle      1000 x 6   http://www-aig.jpl.nasa.gov/public/mls/time-series/
winding      2500 x 7   http://www.esat.kuleuven.ac.be/~tokka/daisydata.html
phone        1708 x 8   http://www.teco.edu/tea/datasets/phone1.xls
exrates      2567 x 12  http://www.stat.duke.edu/data-sets/mw/ts_data/all_exrates.html
djdc0093    26612 x 2   http://lib.stat.cmu.edu/datasets/djdc0093
djia16K     16384 x 1   http://lib.stat.cmu.edu/datasets/djdc0093 (filtered)
synthetic  100001 x 10  http://kdd.ics.uci.edu/databases/synthetic/synthetic.html

Comparison Tools


Dataset:
    B: -

    n: -



    Sunday, March 2, 2008

    Juha Karkkainen and Peter Sanders's Linear Suffix Array Construction (JavaScript Demo)

    Input your own the text string in the textfield below and click generate.

    The paper for this algorithm can be found at Juha Karkkainen's site : http://www.cs.helsinki.fi/u/tpkarkka/.

    If you like this demo, you may also like to see Linear Suffix Tree construction's demo by Ukkonen.

    Ukkonen's Linear Suffix Tree Construction (JavaScript Demo)

    Input your own the text string in the textfield below and click generate.
    Don't forget to put a unique ending character for the text (usually it's '#').

    Show Every Split

    Each node in the picture is represented by NODE_INDEX(SUFFIX_LINK, SUBSTRING, START_INDEX, END_INDEX) where:

    • NODE_INDEX is an integer generated as new node is created.
    • SUFFIX_LINK is an integer that tells the suffix link of the node.
    • SUBSTRING is the string connecting the parent node to this node.
    • START_INDEX and END_INDEX is the index of the substring in the text.

    The JavaScript code skeleton is taken from http://www.csse.monash.edu.au/~lloyd/tildeAlgDS/Tree/Suffix/. You will find also the step-by-step explanation in creating Suffix Tree for text "mississipi" in that site.

    You also can use these text for more understanding on how it works:

    If you like this demo, you may also like to see Linear Suffix Array construction's demo by Juha Karkkainen and Peter Sanders.

    Friday, December 14, 2007

    ACM ICPC Singapore 2007

    This is the first time NUS (National University of Singapore) host an ACM ICPC Regional. Singapore is a very strategic place for Asian teams because of free VISA (for ASEAN) and many direct planes to Singapore are available. This attracted many great teams all over Asia to join regional Singapore which made Regional Singapore one of the toughest Regional in Asia. The surprise prize for the champion team of this regional is 3 Lenovo (each worth around 3000 SGD)! This is a very attractive prize!! I'm sure the next 4 years (when NUS host the ACM ICPC Regional again), there'll be even more teams join Regional Singapore merely to get the prizes.

    This time I represent NUS instead of BINUS UNIVERSITY. My team name is TOKIERS 020606 which consist of 3 TOKI (Tim Olimpiade Komputer Indonesia) members:

    1. Felix Halim, it's me from TOKI 2002 (Computer Science post-grad, first year).
    2. Aditya Kristanto Goenawan, from TOKI 2006 (Engineering under-grad, first year).
    3. Daniel Aris Pandu Prasetia, from TOKI 2006 (Engineering under-grad, first year).
    FYI, TOKI is a government organization from Indonesia that manage, train, and select four Indonesian high-school students to be sent to IOI (International Olympiads in Informatics) contest every year. Surprisingly, there were a lot of TOKI alumnis (total 18 person) joined this year Singapore Regional contest. It's like a reunion of TOKI-ers :) If we count the total number of Indonesian contestants, it's is even greater: 6 universities from Indonesia (BINUS, ITB, Parahyangan, UI, USU, Telkom) + 1 from NUS (TOKIERS 020606) + 1 team from NTU (Ivory Gull), and several other teams also contains Indonesian contestant. Indonesian students are getting better and better in algorithm skills.


    Left to Right: Aditya KG, Daniel APP, and Felix Halim

    Registration and Practice Session - December 13, 2007

    My brother, Steven Halim, is one of the Registration committee for this event. He is the one who gave you the ICPC Bag, T-Shirt, Name Tag, etc. So I think every team should've met my brother :). Since NUS were short in number of committee, the Registration committees ended up being "any" committee which took care of almost anything :(. They worked really hard to make this event sucessful. Thanks to them.

    After registration, the 47 teams (except JiLin University who didn't manage to come due to VISA problem) went to the practice room. A bottleneck happened during the move so the practice session was delayed a bit. In this practice session, there were 3 easy problems given. However the last one is troublesome. My team and several other teams didn't manage to get it Accepted! The reason is the scanf problem. Those who used scanf("%d\n",&n) to read the number of testcases will never get this problem Accepted. Examine a C code below:

    #include <stdio.h>
    
    char s[1000];
    int n;
    
    int main(){
        scanf("%d\n",&n);
        for (int i=0; i<n; i++){
            gets(s);
            
            // process s here... 
            puts(s);
        }
    }
    
    If the input is something like:
    3
    
    felix
    halim
    
    Then the output is surprisingly wrong:
    felix
    halim
    halim
    
    The "\n" in scanf actually read two new lines! This is something unexpected. This is a lesson to never use scanf("%d\n", ...) if the input can be a blank line! There are several teams got trapped with this kind of thing including mine. Other than this issue, the rest of the practice session went smoothly.

    ACM ICPC Regional Singapore - December 14, 2007

    The contest starts at 8:30am and ends at 1:30pm. I got there Just in TimeTM :D to give a little excitement to the other teams. They thought I wouldn't come because probably I joined the last night SRM and was too tired to get up in the morning but then I dissapointed them by showing my face, hihihi. Whereas the fact is: I woke up late :P, causing panic to my own team :P (sori Daniel, Aditya)

    Then the contest starts. As usual, I coded the template REP, FOR, using std, etc... just like TopCoder style (see my code below). Then the fun begins! The 7 problem statements of this regional contest can be found here.

    Problem A - MODEX (Accepted in minute 7)

    When I started reading, Aditya KG asked me if I know something about big-mod and pointed me to Problem A. With years of experience, looking at the big-mod was like going back to childhood :P. With no further ado, I coded and submit it. It's not a surprise that the other teams also get this problem accepted very quick.

    The problem asked to compute x^y mod n. Where y can be as large as 2^31 - 1. The must-know-rule of big mod is: (a*b) mod c is equal to ((a mod c) * (b mod c)) mod c. The solution is to divide the y into two until it reaches the base case of y = 0. Then construct the solution based on the result of the smaller y. The dynamic programming goes like this:

    x^y mod n is equal to

    • 1 (one) if y is equal to 0 (zero)
    • ((x^(y/2) mod n) * (x^(y/2) mod n)) mod n, if y is even
    • ((x^(y div 2) mod n) * (x^(y div 2) mod n) * x) mod n, if y is odd (where div is an integer division)
    The C code is as follows:
    #include <stdio.h>
    
    int pow(int x, int y, int n){
        if (y==0) return 1;
        int t = pow(x,y/2,n);
        if (y%2==0) return (t*t) % n;
        return (((t*t)%n) * x) % n;
    }
    
    int main(){
        int nTC, x,y,n;
    
        scanf("%d",&nTC);
        while (nTC--){
            scanf("%d %d %d",&x,&y,&n);
            printf("%d\n",pow(x,y,n));
        }
    }
    
    What's intriguing about this problem is that, if you use Java, you can just use the BigInteger class! (Randy Sugianto told me this)
    import java.util.*;
    import java.math.*;
    import java.io.*;
    
    public class A {
        public static void main(String[] args) throws IOException {
            Scanner scan = new Scanner(System.in);
            int nTC = scan.nextInt();
            while (nTC-- > 0){
                BigInteger x = BigInteger.valueOf(scan.nextInt());
                BigInteger y = BigInteger.valueOf(scan.nextInt());
                BigInteger n = BigInteger.valueOf(scan.nextInt());
                BigInteger z = x.modPow(y,n); // look ma, it's in the library ;)
                System.out.println( z );
            }
        }
    }
    
    Problem A was supposed to be a super bonus problem.

    Problem C - ACORN (Accepted in minute 56)

    Another DP (Dynamic Programming) problem. Hmm... It's really strange that this kind of problem is now becomes not interesting anymore. In early days when I was just starting to learn programming, I always thought that Dynamic Programming problems are hard, but nowdays these kind of problem starts to lose it's value. It's already becomes an easy-standard problems. Once you know the trick, the rest is just coding skills.

    As we all know we have two ways to do DP: bottom-up and top-down. The bottom-up DP can save memory and more efficient but the code is not intuitive sometimes. The top-down DP is very easy to code (intuitive) but takes up more memory and run time. The code for both kinds of DP is presented below.

    The problem is like this: There are t trees with the same height h. There are acorns distributed in different heights of any tree. A squirrel is currently at the top of a tree and want to go down and collect as many acorns as possible. The squirrel can choose to go down one step from height x to height x-1 or it can choose to fly to another tree from height x to height x-f (f is given). The question is what is the maximum number of acorns the squirrel can collect?

    This is the bottom-up DP version:

    #include <stdio.h>
    #include <string.h>
    
    #define REP(i,n) for (int i=0,_n=(n); i<_n; i++)
    #define FORD(i,a,b) for (int i=(a),_b=(b); i>=_b; i--)
    
    #define MAXN 2007
    
    int dp[MAXN];
    int tree[MAXN][MAXN];
    int t,h,f,a,n,nTC;
    
    int main(){
        scanf("%d",&nTC);
        while(nTC--){
            memset(dp,0,sizeof(dp));
            memset(tree,0,sizeof(tree));
    
            scanf("%d %d %d",&t,&h,&f);
            REP(i,t){
                scanf("%d",&a);
                REP(j,a){
                    scanf("%d",&n);
                    tree[i][n]++;
                }
            }
    
            FORD(i,h,1) REP(j,t){
                int best = tree[j][i+1]; // don't jump
                best >?= dp[i+f];        // jump
                best += tree[j][i];      // add current
                tree[j][i] = best;       // update
                dp[i] >?= best;          // update
            }
            printf("%d\n",dp[1]);   
        }   
    }
    

    This is the top-down DP version:

    #include <stdio.h>
    #include <string.h>
    
    #define REP(i,n) for (int i=0,_n=(n); i<_n; i++)
    
    #define MAXN 2001
    
    int memo[MAXN][MAXN];
    int tree[MAXN][MAXN];
    int t,h,f,a,n,nTC;
    
    int rec(int hi, int ti){
        if (hi>=h) return 0;
        int &ret = memo[hi][ti];
        if (ret!=-1) return ret;
        ret = tree[ti][hi] + rec(hi+1,ti);              // don't jump
        REP(i,t) ret >?= tree[ti][hi] + rec(hi+f, i);   // jump
        return ret;
    }
    
    int main(){
        scanf("%d",&nTC);
        while(nTC--){
            memset(memo,-1,sizeof(memo));
            memset(tree,0,sizeof(tree));
    
            scanf("%d %d %d",&t,&h,&f);
            REP(i,t){
                scanf("%d",&a);
                REP(j,a){
                    scanf("%d",&n);
                    tree[i][n-1]++;
                }
            }
    
            int res = 0;
            REP(i,t) res >?= rec(0,i);
            printf("%d\n",res);
        }
    }
    
    Daniel coded this problem using bottom-up dynamic programming and got it accepted in the first try.

    Problem B - JONES (Accepted in minute 102, penalty 5 x 20)

    IIRC, Aditya KG was doing problem F and I was doing problem G while Daniel was looking for another problem. The ranklist at this time is not good :( Our team solved 2 problems while more than ten other teams solved 3+ problems. What made me hot was that YoiMon was above us! This team should not be allowed to go any further!! :P

    Problem G was also a typical problem: given a simple graph, you must select and remove a set of edges from the graph such that the graph has no cycle in it and the total weight of the removed edges is minimum. I thought problem G can be solved using greedy by finding the smallest edge (smallest weight) and check whether it is possible to create a cycle using that edge (using DFS). If can then I can just remove that edge and find the second smallest edge and so on. When I submit it, I got Wrong Answer. I was quite sure that this should work. Many teams already got this problem accepted, I was quite frustrated and decided to try the other problem.

    Then Aditya KG took over the keyboard to code problem F while I'm hunting for another problem. Problem B (Jones) got my attention. By this time, Aditya KG had submit problem F several times but keep getting Wrong Answer.

    Problem B is about Indiana Jones want to cross a river by hopping through stones. Each stones can sink at certain interval and can resurface in another interval or stand still. The exact interval of the sink/resurface is given. The question is what is the earliest time Indiana Jones can reach the other side of the river by jumping through these "magic" stones.

    Problem B seemed too simple at the beginning and I was underestimating the problem. I thought the solution was just a simple DP (again): The Indiana Jones can jump forward to the next stone anytime he can or wait for the next stone to resurface (if it's currently sinking) and choose the best outcome. I robbed the keyboard and coded simple top-down DP "in no time" and then submit it (when I said "in no time", it actually consumes about 5 to 15 minutes :P). Then gave the keyboard back to Aditya KG as if Problem B already accepted, hehe :P.

    To my surprise, my solution for problem B got Wrong Answer. I was too confident. When I printed the code, I realized that my code is totaly wrong: the variable name was switched (doh!). I fixed it and resubmit. Those of you who know the solution should know already that this submission will also get Wrong Answer. My algorithm was missing something important. That is: Indiana Jones can also jump back to the previous stones besides waiting or jumping forward. Realizing this mistake, I slipped in a few lines of code to the top-down DP to allow Indiana Jones to jump backward, and resubmit. At this time, the code was having too many patches and got Wrong Answer again :(. During this time, I bothered Aditya KG several times when he coded problem F. Maybe this is the reason he couldn't get problem F accepted :D, sorry Aditya. I calmed myself, and start to simplify Problem B code, resubmit and got it accepted at last. This is the code:

    #include <stdio.h>
    #include <string.h>
    
    #define REP(i,n) for (int i=0,_n=(n); i<_n; i++)
    #define FOR(i,a,b) for (int i=(a),_b=(b); i<=_b; i++)
    
    int nTC,s,t;
    char con[550][550], temp[10];
    int memo[550][550];
    
    int rec(int time, int idx){
        if (idx==s) return time;
        if (time>=t) return 1000;
        int &ret = memo[time][idx];
        if (ret!=-1) return ret; else ret = 1000;
        if (con[time][idx]==0) return ret;
        
        FOR(i,time,t-1){
            if (con[i][idx]==0) break;
            if (con[i][idx+1]) ret <?= rec(i+1, idx+1);
            if (idx>0 && con[i][idx-1]) ret <?= rec(i+1, idx-1);
        }
        return ret;
    }
    
    int main(){
        scanf("%d",&nTC);
        while (nTC--){
            scanf("%d %d",&s,&t);
            memset(con,0,sizeof(con));
            REP(i,t){
                REP(j,s){
                    scanf("%s",temp);
                    con[i][j] = temp[0];
                }
                con[i][s] = 1;
            }
            REP(i,s){
                int c = 1;
                REP(j,t){
                    if (con[j][i]=='u'){
                        con[j][i] = c;
                    } else if (con[j][i]=='r'){
                        con[j][i] = c = 1;
                    } else {
                        con[j][i] = c = 0;
                    }
                }
            }
            con[t][s] = 1;
    
            memset(memo,-1,sizeof(memo));
            int res = 1000;
            REP(i,t-1) if (con[i][0]) res <?= rec(i+1,0);
            if (res>=1000) puts("-1");
            else printf("%d\n",res);
        }
    }
    

    Problem F - USHER (Accepted in minute 129, penalty 3 x 20)

    The ranklist at this point was also not good, no need to tell the details (I'm embarrassed :P). Feeling guilty to Aditya which keyboard got robbed by me several times, I decided to rob his problem as well (how cruel me!!) since I didn't have clue for other problems yet. I asked Daniel to re-explain the problem F again. He explained to me: There is a box which can be inserted up to certain amount of dollar coins in it. This box is passed from the usher to several parishioners and the parishioners can add certain amout of dollar in it and pass it back to usher or other parishioners and so on. The box passing stops when the box is full of coins. Every time the usher get the box, he will remove one coin, add it to his pocket and the passing continues. Usher never add coin to the box. The question is what is the maximum number of coins the usher can get?

    Floyd Warshall algorithm immediatelly popped out from my mind (FYI, it's another DP algorithm). That is, just find the minimum cost cycle from usher to parishoioners and back to the usher again. The result is the box capacity divided by the cycle cost. I immediatelly coded it "in no time" again and submit it. I had a feeling that simple division wouldn't work but Daniel was fine-fine aja with that so I submit it anyway. It's my fault that I didn't tell Daniel my doubt :(. After got Wrong Answer, Daniel then remembered that he forgot to tell me that after dividing the box capacity with the shortest cycle length (or cost), we need to calculate again the possibility of having more cycle (because the usher removed a coin). Thus, the usher may end up having more coins. Aditya KG already have the formula to calculate this, I can just use his, resubmit and got it accepted. It was just a slight error, anyway, good work Aditya and Daniel. Below is our code for problem F:

    #include <stdio.h>
    #include <string.h>
    
    #define REP(i,n) for (int i=0,_n=(n); i<_n; i++)
    #define FOR(i,a,b) for (int i=(a),_b=(b); i<=_b; i++)
    
    int nTC, cost, n, box, con[510][510], nr, a,b;
    
    int main(){
        scanf("%d",&nTC);
        while (nTC--){
            scanf("%d %d",&box,&n); n++;
    
            memset(con,-1,sizeof(con));
    
            scanf("%d",&nr);
            REP(i,nr){
                scanf("%d",&b);
                con[0][b] = 0;
            }
            
            FOR(i,1,n-1){
                scanf("%d",&nr);
                REP(j,nr){
                    scanf("%d %d",&a,&b);
                    if (con[i][b]==-1 || a<con[i][b]) con[i][b] = a;
                }
            }
            
            // Floyd Warshall
            REP(k,n) REP(i,n) if (con[i][k]!=-1)
                REP(j,n) if (con[k][j]!=-1){
                    if (con[i][j]==-1 || con[i][j] > con[i][k] + con[k][j])
                        con[i][j] = con[i][k] + con[k][j];
                }
            
            int cycleLen = 1000000000;  
            FOR(i,1,n-1) if (con[0][i]!=-1 && con[i][0]!=-1)
                cycleLen <?= con[0][i] + con[i][0];
    
            // Aditya KG's formula
            cycleLen--;
            int tot = box/cycleLen;
            while ((tot*cycleLen) + cycleLen+1 >= box) tot--;
            printf("%d\n",tot+1);
        }
    }
    

    Problem G - RACING (Accepted in minute 164, penalty 1 x 20)

    This time, our team was still below YoiMon but we have the same number of solved problems :). However the rank was still bad enough, we need to solve more problems to get to the top 10.

    Fortunately, out of the blue, Daniel mentioned something about Minimum Spanning Tree for problem G. When I think about it again, the algorithm seemed fit to the problem! Since we need to remove the cycles from the graph by removing edges, this description is like creating a tree. When I think about what the tree should look like, it "clicks" with the Maximum Spanning Tree. Knowing this, coded problem G, again, "in no time", submit it and got it accepted, Yatta!! At least there wass some period of time where TOKIERS 020606 managed to surpass YoiMon :D Below is our code for problem G:

    #include <stdio.h>
    #include <string.h>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    #define REP(i,n) for (int i=0,_n=(n); i<_n; i++)
    #define FOR(i,a,b) for (int i=(a),_b=(b); i<=_b; i++)
    
    #define MAXN 10010
    
    // Union-Set algorithm
    int pset[MAXN];
    void initset(){ REP(i,MAXN) pset[i] = i; }
    int findSet(int i){ return (pset[i]==i)? i: (pset[i] = findSet(pset[i])); }
    void merge(int i, int j){ pset[findSet(i)] = findSet(j); }
    int sameSet(int i, int j){ return findSet(i)==findSet(j); }
    
    int nTC,n,m;
    char vis[MAXN];
    
    int main(){
        scanf("%d",&nTC);
        while (nTC--){
            scanf("%d %d",&n,&m);
            vector<pair<int,pair<int,int> > > junc;
            REP(i,m){
                int a,b,c;
                scanf("%d %d %d",&a,&b,&c); a--; b--;
                junc.push_back(make_pair(c,make_pair(a,b)));
            }
    
            // sort edges descending by weight
            sort(junc.rbegin(), junc.rend());
    
            // Maximum Spanning Tree
            memset(vis,0,sizeof(vis));
            initset();
            int res = 0;
            REP(i,junc.size()){
                int a = junc[i].second.first;
                int b = junc[i].second.second;
                int c = junc[i].first;
                if (sameSet(a,b)) res += c;
                else merge(a,b);
            }
            printf("%d\n",res);
        }
    }
    

    The rest of the contest

    I already found the algorithm for problem D which runs in O(n^2 log n) also problem E which is kind of gambling (unsure if my algo will pass the time limit). The remaining time is about 1 hour and 20 minutes, I decided to go with problem D and told Aditya and Daniel to think of faster algorithm for problem E.

    It turned out to be a bad idea, problem D is quite hard to implement and I got bugs in the code I couldn't find :(, now it's 30 minutes left, the ranklist was freezed. This was really a problematic situation. If I recoded problem D, it won't finish in time. Aditya and Daniel couldn't find faster algorithm for problem E either. So I focused on problem D but still didn't managed to pass the sample case :(. I guess this is the end of our strength.


    TOKIERS 020606 in Action!

    After contest

    Many teams asked me what's wrong with their solution for Problem B. Why they got so many Wrong Answers. They asked me what was the tricky case? I was surprised since I didn't find any tricky case for Problem B. I got WA previously because my algo and code was wrong and there were so many changes before I got it accepted. So I couldn't remember which one I changed to make it accepted. When I looked at the standings, the top teams also didn't manage to get it accepted and there were exceptionaly so many Wrong Answers on problem B! I suspect there is something fishy about this problem. Maybe there is an unwritten boundary case? But why some of the team got it accepted? The judges at that time concluded that the judge output was correct. Nobody could figure out what's wrong with Problem B.

    After having lunch and city tour in the afternoon, the award ceremony begins. Only the top 5 teams got awards. The award ceremony was merged with the other three SoC competitions. This made ACM ICPC Singapore looked a lot less prestigious (seemed only a small part of SoC event). It was funny to see high-schoolers and middle-schoolers came up from nowhere, got awarded then paired with the champion team of ACM ICPC. We were totaly un-aware of those competitions and it felt awkward. Anyway, below is the official standings. The light-green-ed teams contain Indonesian contestants.

    RankUniversity NameTeam NameCitation# SolvedTime
    1Shanghai Jiao Tong UniversityPrimeFirst Place7569
    2Zhejiang UniversityOthelloSecond Place7641
    3Shanghai Jiao Tong UniversitySofasquadThird Place6470
    3Peking UniversityExcaliburThird Place6603
    4Zhongshan(Sun Yat-sen) UniversityZSU_PhecdaFourth Place6650
    5Fudan UniversityMetaphorFifth Place6757
    5BINUS UNIVERSITYYoiMonFitth Place6765
    5National Tsing Hua UniversityB.F.WFifth Place61044
    8Nanyang Technological UniversityNTU Ivory GullEighth Place5328
    8Taiwan Universityrand N-SATEighth Place5379
    8Nanyang Technological UniversityNanyang EaglesEighth Place5607
    8National University of SingaporeTOKIERS 020606Eighth Place5812
    9Institut Teknologi BandungGanesha ITBNinth Place5813
    9University of the Philippines DilimanUP Fighting MoronsNinth Place5854
    9Shanghai UniversityLarvaNinth Place5871
    10Saitama UniversityMaximum-CoffeeTenth Place5925
    10Saitama UniversityMaximum_TGMTenth Place5934
    11National University of Singapore12TIN@NUSEleventh Place51046
    13National University of SingaporeeagleThirteenth Place4382
    14National University of SingaporeCode WarriorFourteenth Place4488
    14Nanyang Technological UniversityKhANFourteenth Place4664
    15Nanyang Technological UniversityY2FFifteenth Place4718
    15National University of SingaporeNUSSOCFifteenth Place4719
    15Nanyang Technological UniversityGeJeFifteenth Place4778
    15BINUS UNIVERSITYACMonFifteenth Place4847
    15City University of Hong Kong9.99 AcceptedFifteenth Place3219
    15Nanyang Technological UniversityHiWorldFifteenth Place3234
    15National Tsing Hua UniversityROHFifteenth Place3369
    15Kasetsart University, ThailandT2BFifteenth Place3385
    16Nanyang Technological UniversityTMNQSixteenth Place3393
    16International Islamic University MalaysiaAl-KhawarizmiSixteenth Place3440
    16Parahyangan UniversityH&&HSixteenth Place3495
    17Soongsil UniversityInspirationsSeventeenth Place3525
    18Universitas IndonesiaMakaraEighteenth Place3532
    21University of Sumatera UtaraUSU - The Last OneTwenty-first Place284
    22National Chiao Tung UniversityFreshfoxTwenty-second Place2168
    23Parahyangan UniversityKiWiChanTwenty-third Place2307
    Ateneo de Manila UniversitycxxCHonorable Mention
    Informatics EducationPolymorphicHonorable Mention
    University of Sumatera UtaraUSU - The Last TwoHonorable Mention
    National University of SingaporeCoding BunniesHonorable Mention
    Soongsil UniversityNewFaceHonorable Mention
    Multimedia University, Malaysia!Honorable Mention
    Nanyang Technological UniversityCODE BREAKERSHonorable Mention
    Multimedia University, MalaysiaInfinityHonorable Mention
    Prince of Songkla UniversityPSU-PKTHonorable Mention
    Sekolah Tinggi Teknologi TelkomSTT01Honorable Mention

    The truth about Problem B weirdness

    The judge reference solution was wrong. The reference solution assumed that Indiana Jones cannot jump at Interval 0 (zero). Thus for a certain testcase which has a possible path using Interval 0 (zero), the reference solution won't select that path. The judges should've suspect something fishy when the top teams which can get a problem accepted in a single submission, got this problem wrong for more than 10 submissions!

    As the result, few days after the contest was over, all the solutions submitted for Problem B was re-judged and the ranklist was updated. The above ranklist is the latest and final one. This is a very common problem in programming contests. Even in ACM ICPC World Finals, this kind of mistakes still happened. The best way to deal with this problem is that the judges must be aware of fishy problems during the contest. For example by examining the source codes of the contestants, not only by looking at their output only. It's possible that their code will give hints to find out the problems (especialy top teams codes!).

    However, if the judges didn't manage find the mistakes during the contest time and after the award ceremony... Then there are two choices to remedy this problem: Leave the standing as if there was no problem and decide it's final or keep trying to find out the fishy problem and try to fix (if possible, re-judge) and annouce it to the contestants. I'm happy that NUS Contest Director chose the latter. The changes of the standings will impact on the wild-card decision later on (which is crucial for some top teams).

    Looking at the changes of the standings, I can tell that the re-judge was done by removing the test-cases which contains Interval 0 (zero) solution. By removing test-cases which solutions involving Interval 0 (zero), this will Accept both solutions that consider or not consider Interval 0 (zero). In other words, those teams who already got Accepted will still remain get Accepted and those teams that got Wrong Answer may get Accepted after it's re-judged.

    My comment about the contest

    Other than the Problem B issue, I enjoyed the ACM ICPC Singapore since I can meet the other Indonesian coders and have fun with them. The problem set in overall was very easy. If there were no issue with Problem B, perhaps the champion team can solve all the 7 problems in only three out of five hours. Those teams literally code "in no time" :D.


    Indonesian teams took photo together before going back.

    Special Thanks

    • Dr Sun Teck TAN, Contest Director, ACM ICPC Singapore Site
    • Registration (any) committees, Steven Halim et. all.
    • Judges, Melvin Zhang et. all. and the problem setters.
    • Photographer from YoiMon, Andrian Kurniady. Thanks kur for the great camera and also thanks to Suhendry who took the pictures for me and my team.

    External links

    Let me know if there are any other blogs/news.

    Saturday, June 16, 2007

    Final Indonesia National Contest 2007 :: Problem H - Tree Median

    ACM/ICPC Indonesia National Contest 2007

    Problem H

    Tree Median

    Time Limit: 3s


    A tree is a connected graph containing no cycles. A vertex is called a median vertex if the total cost to reach all vertices is minimal. There could be more than one median vertex in a tree, that's why we define median as the set of all median vertices. To find median in a tree with small number of vertices is fairly easy task as you can solve this by a simple brute force program.

    In the left figure, we can see a weighted tree with 5 vertices. The tree median is {B} because the total cost from vertex B to all other vertices is minimal.

    B-A = 2    B-D = 7
    B-C = 1    B-E = 7 + 5 = 12
    
    TOTAL = 2 + 1 + 7 + 12 = 22
    

    What if the number of vertices is quite large? This might be a problem since brute force program cost too much time. Given a weighted tree with N vertices, output the total cost to reach all vertices from its median.

    Input

    Input consists of several cases. Each case begins with an integer n (1<= n <= 10,000) denoting the number of vertices in a tree. Each vertex is numbered from 0...n-1. Each of the next n-1 lines contains three integers: a, b, and w (1<= w <= 100), which means a and b is connected by an edge with weight w.

    Input is terminated when n is equal to 0. This input should not be processed.

    Output

    For each case, output a line containing the sum of cost of path to all other vertices from the tree median.

    Sample Input

    5
    0 1 2
    1 2 1
    1 3 7
    3 4 5
    6
    0 1 1
    1 2 4
    2 3 1
    3 4 4
    4 5 1
    0
    

    Sample Output

    22
    21
    


    Problem Setter: Suhendry Effendy



    Pembahasan Problem H - Tree Median

    Diberikan suatu graph berbentuk tree. Graph ini memiliki N (1 <= N <= 10000) nodes dan N-1 edges yang mempunyai weight W (1 <= W <= 100). Carilah satu dari N node untuk dijadikan "root" sehingga total distance dari root ke semua nodes adalah minimum. Untuk contoh jelasnya (dipandu dengan gambar) silahkan lihat problem statement diatas.

    Salah satu cara menyelesaikannya adalah dengan bruteforce, yaitu dengan mencoba satu-persatu node untuk dijadikan "root" dan menghitung total distance dari root tersebut ke semua nodes. Kendalanya adalah bagaimana cara menghitung total distance dari root ke semua node dalam O(1)? Berikut adalah cara yang saya ketahui, yaitu dengan menggeser root yang sekarang ke node tetangganya dan meng-update total distance ke semua nodes. Dibawah adalah langkah-langkah untuk melakukannya:

    Pilih salah satu node untuk dijadikan "root", misalkan node 0. Lalu pre-calculate semua distance weight dan count nodes dari root ke semua nodes. Definisi distance weight adalah berapa total distance untuk node tersebut beserta sub-tree dari node tersebut. Definisi count nodes adalah berapa banyak node anak yang dimiliki node tersebut (termasuk node itu sendiri). Pre-calculate ini bisa dilakukan dengan DFS (rekursi). Note: untuk input yang besar, misalkan N = 10000, maka kedalaman rekursi dari DFS ini akan menyebabkan stack-overflow kecuali anda sudah mengubah setting compiler untuk stack size menjadi lebih besar (32 MB cukup).

    Setelah pre-calculate distance weight dan count nodes dari node 0, otomatis anda bisa mengetahui total distance dari root ini ke semua nodes dalam O(edges dari node 0). Lalu anda bisa melakukan DFS (rekursi) ke dua yang bertugas untuk menggerakan "root" (yang sekarang di node 0) ke semua node lainnya sembari mengupdate tabel distance weight dan count nodes supaya anda bisa tetap menghitung total distance dari root yang baru ke semua nodes juga dalam O(edges dari root tersebut).

    Jadi, total complexity dari algoritma ini adalah O(N) untuk pre-calculate distance dan count. O(N) untuk menggeser "root" ke N-1 nodes yang lain sambil mengcalculate distance nya dalam O(edges dari root node baru tersebut). Dengan amortized analysis, complexity dari calculate total distance untuk suatu root adalah O(1), karena ada N-1 edges total dan N nodes.
    Code untuk problem ini saya pecah menjadi dua: tree-rec.cpp dan tree-stack.cpp. Keduanya mengimplement code diatas. Tetapi yang menggunakan rekursi (tree-rec.cpp) hanya bisa untuk N yang kecil (karena terbatas stack size di compiler, kecuali anda naikkan stack size compiler anda). Untuk code yang menggunakan stack buatan (tree-stack.cpp) bisa menghandle input lebih besar tanpa mengubah settingan compiler karena data dialokasikan di heap memory.

    Kedua code menggunakan STL map, set, vector. Kalau tidak, maka codingnya akan jauh lebih panjanggggg..... Anda bisa menggunakan problem ini untuk belajar STL maupun rekursi dengan stack buatan.

    Untuk Andrian Kurniady: sharing donk algo "DP problem I" nya gimana? pake rekursi topdown? apa bottom up?

    #include <stdio.h>
    #include <string.h>
    #include <set>
    #include <map>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    #define FOREACH(it,arr) for (__typeof((arr).begin()) it=(arr).begin(); it!=(arr).end(); it++)
    
    vector<map<int,int> > con;
    vector<long long> dist;
    vector<int> cnt;
    long long res;
    int n;
    
    void calcCnt(int i, int p){
        cnt[i]++;
        dist[i] = 0;
        FOREACH(it,con[i]){
            int j = it->first;
            int w = it->second;
            if (j!=p){
                calcCnt(j,i);
                cnt[i] += cnt[j];
                dist[i] += cnt[j] * w + dist[j];
            }
        }
    }
    
    void rec(int i, int p, int c, long long v){
        long long sum = (p==-1)? 0 : (v + c * con[p][i]);
        FOREACH(it,con[i]){
            int j = it->first;
            int w = it->second;
            if (j!=p) sum += cnt[j] * w + dist[j];
        }
        res = min(res, sum);
    
        FOREACH(it,con[i]){
            int j = it->first;
            int w = it->second;
            if (j!=p) rec(j, i, n-cnt[j], sum-(dist[j] + cnt[j]*w));
        }
    }
    
    int main(){
        while (scanf("%d",&n)!=EOF && n){
            con = vector<map<int,int> >(n);
            for (int i=1,a,b,w; i<n; i++){
                scanf("%d %d %d",&a,&b,&w);
                con[a][b] = w;
                con[b][a] = w;
            }
    
            cnt = vector<int>(n);
            dist = vector<long long>(n);
            calcCnt(0,-1);
    
            res = 1000000000000LL;
            rec(0,-1,0,0);
            printf("%lld\n",res);
        }
    }
    
    #include <stdio.h>
    #include <string.h>
    #include <set>
    #include <map>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    #define FOREACH(it,arr) for (__typeof((arr).begin()) it=(arr).begin(); it!=(arr).end(); it++)
    
    vector<map<int,int> > con;
    vector<long long> dist;
    vector<int> cnt;
    long long res;
    int n;
    
    void calcCnt(int i, int p){
        vector<pair<int,int> > stk;
        vector<bool> vis;
        stk.push_back(make_pair(i,p));
        vis.push_back(false);
        while (stk.size()>0){
            i = stk.back().first;
            p = stk.back().second;
    
            if (!vis.back()){
                vis.back() = true;
                FOREACH(it,con[i]){
                    int j = it->first;
                    int w = it->second;
                    if (j!=p){
                        stk.push_back(make_pair(j,i));
                        vis.push_back(false);
                    }
                }
            } else {
                cnt[i] = 1;
                dist[i] = 0;
                FOREACH(it,con[i]){
                    int j = it->first;
                    int w = it->second;
                    if (j!=p){
                        cnt[i] += cnt[j];
                        dist[i] += cnt[j] * w + dist[j];
                    }
                }
                stk.pop_back();
                vis.pop_back();
            }
        }
    }
    
    struct ipcv {
        long long i,p,c,v;
    };
    
    void rec(int i, int p, int c, long long v){
        vector<ipcv> stk;
        vector<bool> vis;
        stk.push_back((ipcv){i,p,c,v});
        vis.push_back(false);
        while (stk.size()>0){
            ipcv t = stk.back();
            i = t.i;
            p = t.p;
            c = t.c;
            v = t.v;
    
            if (!vis.back()){
                vis.back() = true;
    
                long long sum = (p==-1)? 0 : (v + c * con[p][i]);
                FOREACH(it,con[i]){
                    int j = it->first;
                    int w = it->second;
                    if (j!=p) sum += cnt[j] * w + dist[j];
                }
                res = min(res, sum);
    
                FOREACH(it,con[i]){
                    int j = it->first;
                    int w = it->second;
                    if (j!=p){
                        stk.push_back((ipcv){j, i, n-cnt[j], sum-(dist[j] + cnt[j]*w)});
                        vis.push_back(false);
                    }
                }
            } else {
                stk.pop_back();
                vis.pop_back();
            }
        }
    }
    
    int main(){
        while (scanf("%d",&n)!=EOF && n){
            con = vector<map<int,int> >(n);
            for (int i=1,a,b,w; i<n; i++){
                scanf("%d %d %d",&a,&b,&w);
                con[a][b] = w;
                con[b][a] = w;
            }
    
            cnt = vector<int>(n);
            dist = vector<long long>(n);
            calcCnt(0,-1);
    
            res = 1000000000000LL;
            rec(0,-1,0,0);
            printf("%lld\n",res);
        }
    }
    

    Kembali ke halaman utama