AOJ2022 Princess, a Cryptanalyst

問題リンク Princess, a Cryptanalyst

  • 解法

2段階を踏んで解きました。
最初は、SSSの長さを求める部分。最後にSSSの文字列を求める部分です。
SSSの長さは
dp[S][last]: 採用した文字列の集合S,最後がlastのときの最小の長さ
のDPで求めることができます。AOJ1320に類題があったりします。
SSSの長さがLと求まったら、再帰で文字列の順列を試していきます。
長さがLを超えたり、現在の解より辞書式に後ろのものになっていたら枝刈りします。
※dp[S][last]を最小の長さで辞書式で最小の文字列 というDPで最初やっていたのですがこれではうまくいきませんでした

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

//Princess, a Cryptanalyst
public class AOJ2022 {

	int n, INF = 1<<29, len;
	int[][] cut, dp;
	String[] s;
	String INFS = "?", res;
	boolean[] u;
	
	void dfs(int k, String str, int pre){
		if(len<str.length()||comp(str, res)>0)return;
		if(k==n){
			res = str; return;
		}
		for(int i=0;i<n;i++){
			if(u[i])continue;
			u[i] = true;
			String nx = "";
			if(pre==-1)nx = s[i];
			else nx = str+s[i].substring(cut[pre][i]);
			dfs(k+1, nx, i);
			u[i] = false;
		}
	}
	
	int get(int S, int last){
		if(dp[S][last]!=INF)return dp[S][last];
		int ns = S-(1<<last);
		if(ns==0)return dp[S][last] = s[last].length();
		int res = INF;
		for(int k=0;k<n;k++){
			if(((ns>>k)&1)==0)continue;
			int r = get(ns, k)+s[last].length()-cut[k][last];
			res = Math.min(res, r);
		}
		return dp[S][last] = res;
	}
	
	int comp(String a, String b){
		if(INFS.equals(a))return 1;
		if(INFS.equals(b))return -1;
		int m = Math.min(a.length(), b.length());
		for(int i=0;i<m;i++)if(a.charAt(i)!=b.charAt(i))return a.charAt(i)-b.charAt(i);
		return 0;
	}
	
	void run(){
		Scanner sc = new Scanner(System.in);
		for(;;){
			int 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=i+1;j<N;j++)if(t[i].equals(t[j]))del[j] = true;
			for(int i=0;i<N;i++)for(int j=0;j<N;j++){
				if(i==j||del[i]||del[j])continue;
				if(t[i].contains(t[j]))del[j] = true;
			}
			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];
			cut = new int[n][n];
			for(int i=0;i<n;i++)for(int j=0;j<n;j++){
				if(i==j)continue;
				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, INF);
			len = INF;
			for(int i=0;i<n;i++)len = Math.min(len, get((1<<n)-1, i));
			res = INFS;
			u = new boolean[n];
			dfs(0, "", -1);
			System.out.println(res);
		}
	}
	
	public static void main(String[] args) {
		new AOJ2022().run();
	}
}