AOJ1320 City Merger

問題リンク City Merger

  • 概要

N個の都市の名前が与えられる。全ての都市の名前を部分列に含み、かつ長さが最小となる文字列を答えよ。
N <= 14
都市名の長さ <= 20

  • 解法

巡回セールスマン問題になります。
dp[S][i]: 訪れた都市の集合S、最後に訪れた都市の番号iのときの最小文字列長
となります。また、ある都市名iが都市名jを完全に含んでいる場合があるとこの方針がうまくいかないので、そのようなjは予め削除しておきます。
あとは、この表をメモ化探索で埋めていくだけです。
cut[i][j]: 都市名iの次に都市名jをつなげるとき重なる部分の最大長
という表を作っておくと計算が楽になると思います。

  • ソース
import java.util.Arrays;
import java.util.Scanner;

//City Merger
public class AOJ1320 {

	int n;
	String[] s;
	int[][] cut;
	int[][] dp;
	
	int get(int S, int last){
		if(dp[S][last]!=-1)return dp[S][last];
		int res = 1<<29;
		int ns = S-(1<<last);
		for(int j=0;j<n;j++){
			if(((ns>>j)&1)==0)continue;
			res = Math.min(res, get(ns, j)+s[last].length()-cut[j][last]);
		}
		return dp[S][last] = res;
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		for(;;){
			n = sc.nextInt();
			if(n==0)break;
			String[] t = new String[n];
			for(int i=0;i<n;i++)t[i]=sc.next();
			boolean[] del = new boolean[n];
			for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(i!=j&&t[i].contains(t[j]))del[j] = true;
			int N = 0;
			for(boolean f:del)if(!f)N++;
			s = new String[N];
			int id = 0;
			for(int i=0;i<n;i++)if(!del[i])s[id++]=t[i];
			n = N;
			cut = new int[n][n];
			for(int i=0;i<n;i++)for(int j=0;j<n;j++){
				for(int k=0;k<s[i].length();k++){
					if(s[j].startsWith(s[i].substring(k))){
						cut[i][j] = s[i].length()-k; break;
					}
				}
			}
			dp = new int[1<<n][n];
			for(int[]a:dp)Arrays.fill(a, -1);
			for(int i=0;i<n;i++)dp[1<<i][i] = s[i].length();
			int res = 1<<29;
			for(int i=0;i<n;i++)res = Math.min(res, get((1<<n)-1, i));
			System.out.println(res);
		}
	}
	
	public static void main(String[] args) {
		new AOJ1320().run();
	}
}