Problem:
MathJax.Hub.Config({ tex2jax: { inlineMath: [["$","$"], ["\\(","\\)"]], processEscapes: true }});

百人一首

百人一首は日本の伝統的なゲームである． このゲームでは N 枚のカードが用いられ，それぞれのカードには1つの文字列が書かれている．細かいルールは省略するが， A君は N 枚のカードに書かれている文字列を何らかの順序でそれぞれちょうど1回ずつ読むことになった．

百人一首ではその接頭辞が非常に重要である．百人一首で使用される文字列として接頭辞は似ている言葉が多く， 連続して似たような文字列を読むとA君も混乱してしまう． そこでA君は，連続する二つのカードの文字列の接頭辞ができるだけ異なるように，カードを並べ替えようと考えた．

A君の目標は，「山札の読みやすさ」と呼ばれる指標を最小化するような山札を求めることである．ここで山札とは，この百人一首で読まれる N 枚のカードを並べ替えたものを指す．山札の読みやすさとは，山札内にある隣接したカードの類似度の総和を言う．2 枚のカードの類似度はそれらに書かれた文字列どうしの最長共通接頭辞の長さとして定義される．なお，文字列 t と u の最長共通接頭辞とは，t と u 両方の接頭辞であるような文字列のうち，最長のものを指す．

例えば，2 枚のカードに書かれた文字列がそれぞれ "jag" と "japan" であれば，これらのカードの類似度は 2 である．一方，"wan" と "nyan" であれば類似度は 0 である．

ところで，「山札の読みやすさ」が最小となる山札は複数存在するかもしれない．A君は，最小解としてありうる山札のうち，辞書順最小の山札を求めたい．ここで，山札 P と Q について P が辞書順で小さいとは，ある正の整数 i が存在して，1 番目から i-1 番目のカードまではそれぞれ同一のカードであり，かつ i 番目のカードの文字列は辞書順において山札 P のカードのほうが小さいことを言う．

あなたの仕事は，「山札の読みやすさ」を最小にするような山札の中で，辞書順最小となるものを求めることである．

Input

入力は複数のデータセットからなる． 各データセットは次の形式で表される．

N
s1
...
sN

1行目にはカードの枚数を表す整数 N (1 ≤ N ≤ 100,000) が与えられる．
続く N 行にはカードに書かれた1文字以上の文字列が書かれてあり， 1行は英小文字のみで構成される．また，同一セット内において，各 si は互いに異なることが保証されている．
さらに，si の長さの合計は 400,000 以下であることが保証されている．

入力の終わりは， 1つのゼロからなる行で示す．

Output

各データセットについて，「山札の読みやすさ」が最小となる山札の中で辞書順最小の山札の情報を，N 行に出力せよ．具体的には，そのような山札の i 番目のカードに書かれた文字列を，i 行目に出力することになる．

Sample Input

3
icpc
icfp
topcoder
4
apple
application
appointment
acmicpc
3
a
aa
aaa
4
a
aza
azb
b
0

Output for Sample Input

icfp
topcoder
icpc
apple
acmicpc
application
appointment
aa
a
aaa
a
aza
b
azb

辞書順の定義に注意すること．今回の定義では，文字列を連結した時に辞書順最小になるということを意味するわけではない．例えば，3番目の入力例では [aaa,a,aa] も「山札の読みやすさ」が最小となる解の一つであり，連結すれば同じ文字列になるため，辞書順最小の解に見えるかもしれない．しかしながら，今回の定義では先頭の文字列 "aa" と "aaa" が優先的に比較されるため，辞書順最小の解は [aa,a,aaa] となる．

Solution in JAVA (judge verdict: Time Limit Exceeded)


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

/**
 * AIZU ONLINE JUDGE
 * 2750 Hyakunin Isshu 百人一首
 *
 *    2018/02/08
 */
public class Main {


    //類似度
    int ruijido(String s0, String s1) {
        int i;
        for(i = 0;; i++) {
            if (i >= s0.length())
                break;
            if (i >= s1.length())
                break;
            if (s0.charAt(i) != s1.charAt(i))
                break;
        }
        return i;
    }

    //類似度の総和
    int sowa(String[] s) {
        int w = 0;
        for(int i = 0; i < s.length - 1; i++) {
            w += ruijido(s[i], s[i + 1]);
        }
        return w;
    }

    int sowa2(int[] a, String[] list) {
        int w = 0;
        for(int i = 0; i < a.length - 1; i++) {
            w += ruijido(list[a[i]], list[a[i + 1]]);
        }
        return w;
    }

    // 接頭辞分析
    //
    int[] head(String[] list) {
        List<Integer> result = new ArrayList<Integer>();
        int hansu = (list.length + 1) / 2; // 半数
        int start = 0;
        int size = list.length;
        for(int n = 0;; n++) { // n文字目
            char ch = 0;
            int cnt = 0;
            int start2 = 0;
            int maxCnt = 0;
            int maxStart = 0;
            for(int i = start; ; i++) {
                if (i < start + size && list[i].length() > n && list[i].charAt(n) == ch) {
                    cnt++;
                }
                else {
                    //log.printf("%d '%c' %d\n", n, ch, cnt);
                    if (cnt > maxCnt) {
                        maxCnt = cnt;
                        maxStart = start2;
                    }
                    if (i >= start + size) {
                        break;
                    }
                    if (list[i].length() > n) {
                        ch = list[i].charAt(n);
                    }
                    else
                        ch = 0;
                    start2 = i;
                    cnt = 1;
                }
            }
            log.printf("%d max %d %s\n", n, maxCnt, list[maxStart]);
            if (maxCnt <= hansu)
                break;
            result.add(maxCnt);
        }

        int[] a = new int[result.size()];
        for(int i = 0; i < result.size(); i++) {
            a[i] = result.get(i);
        }
        log.printf("%s\n", Arrays.toString(a));
        return a;
        
    }



	// メイン return falseでおしまい
	boolean main() throws IOException {

		int[] ir = readIntArray();
		int N = ir[0];
        if (N == 0)
            return false;
        log.printf("N = %d\n", N);

        String[] list = new String[N];

        for(int i = 0; i < N; i++) {
            list[i] = reader.readLine();
        }
        Arrays.sort(list);
        if (N < 10) {
            for(int i = 0; i < N; i++) {
                log.printf("[%s]\n", list[i]);
            }
        }

        int hansu = (list.length + 1) / 2; // 半数

        // 接頭辞分析
        int[] head = head(list);

//        hansu = 10; 
//        head = new int[] { 11, 12, 13 };
        int sa = hansu;
        int mw = 0;  // 類似度の総和の最小値
        
        if (head.length > 0) {
            int n = head.length - 1;
            mw += (head[n] - hansu) * 2 * (n + 1);
            if (list.length % 2 == 0)
                mw -= (n + 1); // 偶数なら最後の１個が半分
            sa = head[n];
            n--;
            for(; n >= 0; n--) {
                mw += (head[n] - sa) * 2 * (n + 1);
                sa = head[n];
            }
        }
        log.printf("mw = %d\n", mw);
//        System.exit(0);
        
        int bestSowa = mw + 1; // すでに求まっている値を上限にする
        int[] bestA = null;

        // 辞書順で全配列を作る
        boolean[] f = new boolean[N];
        int[] a = new int[N];

        int p = 0;
        int sowa2 = 0;
        boolean up = true;
        loop:for(;;) {
            int start;
            if (up) {
                start = 0;
            }
            else
                start = a[p] + 1;

            for(int i = start; i < N; i++) {
                if (!f[i]) {
                    f[i] = true;
                    a[p] = i;
                    if (p > 0)
                        sowa2 += ruijido(list[a[p-1]], list[a[p]]);
                    p++;
                    if (sowa2 >= bestSowa) {
                        break;
                    }
                    if (p >= N) {
                        int sowa = sowa2(a, list);
                        if (sowa < bestSowa) {
                            bestSowa = sowa;
                            bestA = Arrays.copyOf(a, a.length);
                            log.printf("best 完成 %s %d %d\n", Arrays.toString(a), sowa, sowa2);
                        }
                        break;
                    }
                    up = true;
                    continue loop;
                }
            }
            up = false;
            p--;
            if (p < 0)
                break;
            if (p > 0)
                sowa2 -= ruijido(list[a[p-1]], list[a[p]]);
            f[a[p]] = false;
        }

        // 表示
        for(int i = 0; i < a.length; i++) {
            System.out.printf("%s\n", list[bestA[i]]);
        }

		return true; // 正常終了 次へ
	}

	static long time0;
//	private final static boolean DEBUG = true;  // debug 　argsに何か書くとdebugになる
	private final static boolean DEBUG = false; // release

	public static void main(String[] args) throws IOException {

		if (DEBUG || args.length > 0) {
			log = System.out;

			String inputStr = "3:icpc:icfp:topcoder:"; // 1
			inputStr += "0:";

			inputStr = inputStr.replace(":", "\n");

//			reader = new BufferedReader(new StringReader(inputStr));
            reader = new BufferedReader(new InputStreamReader(new FileInputStream("in1.txt"))); // 1 17 98
		}
		else {
			log = new PrintStream(new OutputStream() { public void write(int b) {} } ); // 書き捨て
//            log = System.out;
			reader = new BufferedReader(new InputStreamReader(System.in)); // コンソールから
//            reader = new BufferedReader(new InputStreamReader(new FileInputStream("in.txt")));
		}

		time0 = System.currentTimeMillis();
		int N = Integer.MAX_VALUE;
		//int N = readIntArray()[0];

		for(int i = 0; i < N; i++) {
			boolean b = new Main().main();
			if (!b)
				break;
		}

        long time1 = System.currentTimeMillis();
        log.printf("end time = %.03fsec\n", (time1 - time0) / 1000.);
		reader.close();
	}


	static PrintStream log;
	static BufferedReader reader;


	// 標準入力より1行分の区切り文字区切りでの整数値を読む
	// EOFの場合はnullを返す
	private static int[] readIntArray() throws IOException {

		String s = null;
		for(;;) {
			s = reader.readLine();
//			log.printf("%s\n", s);
			if (s == null)
				return null;
			s = s.trim();
			if (s.length() != 0) // ※※※　どうも突然空行を読むことがある。読み飛ばすとうまくいくらしい。。。。
				break;
		}

		String[] sp = s.split("[ ,]"); // 区切り文字はスペースかカンマ
		int[] a = new int[sp.length];
		for(int i = 0; i < sp.length; i++) {
			a[i] = Integer.parseInt(sp[i]);
		}
		return a;
	}

}