AOJ1291 Search of Concatenated Strings

問題リンク Search of Concatenated Strings

  • 概要

N個の文字列Ei と文字列Sが与えられる。Eiの各要素を1回ずつ連結した文字列が現れる箇所はSの中にいくつあるか答えよ。Eiの連結する順番は任意でよい。例えば、Eiが"a"と"b"であれば、"ab"もしくは"ba"が探すべき文字列となる。
また、出現箇所の個数を数えるため、例えば
Ei: "x", "xx"
S: "xxx"
のとき、"x"+"xx"と"xx"+"x"の2種類が現れるが、種類は問わず、これは1回とカウントする。
連結文字列がオーバラップしている場合は位置がずれるので、複数回カウントする。
Ei: "x", "xx"
S: "xxxx"
の場合、前半の"xxx"部分と、後半の"xxx"部分にそれぞれ連結文字列が出現しているのでこれは2回とカウントする
1 <= N <= 12
1 <= |Ei| <= 20
1 <= |S| <= 5000

  • 解法

DPっぽく解きます。
e[ i ][ j ]: インデックスiを末尾にした文字列を考えたとき、そこに集合 j の文字列を作ることができる
という表を埋めて解きます。jの中のkビット目が1だった場合、Ekを使った連結文字列が作れるという意味です。
この表を先頭から埋めていき、j == 2^N - 1となっているインデックスiの数が答えとなります。
この配列eをbooleanで取ろうとするとMLEで死んでしまうので、byte配列にしました。

  • ソース
import java.util.Scanner;

//Search of Concatenated Strings
public class AOJ1291 {

	void run(){
		Scanner sc = new Scanner(System.in);
		byte[][] e = new byte[5000][1<<12];
		boolean[][] f = new boolean[5000][12];
		for(;;){
			int n = sc.nextInt(), m = sc.nextInt();
			if((n|m)==0)break;
			String[] s = new String[n];
			for(int i=0;i<n;i++)s[i]=sc.next();
			StringBuilder sb = new StringBuilder();
			while(m--!=0)sb.append(sc.next());
			String t = sb.toString();
			int N = t.length();
			for(int i=0;i<N;i++)for(int j=0;j<1<<n;j++)e[i][j]=0;
			for(int i=1;i<=N;i++){
				String sub = t.substring(0, i);
				for(int j=0;j<n;j++){
					f[i-1][j]=sub.endsWith(s[j]);
					if(f[i-1][j])e[i-1][1<<j]=1;
				}
			}
			int res = 0;
			for(int i=0;i<N;i++)for(int S=0;S<1<<n;S++){
				if(e[i][S]==0)continue;
				if(S==(1<<n)-1)res++;
				for(int j=0;j<n;j++)if(((S>>j)&1)==0){
					if(i+s[j].length()<N&&f[i+s[j].length()][j])e[i+s[j].length()][S|(1<<j)]=1;
				}
			}
			System.out.println(res);
		}
	}
	
	public static void main(String[] args) {
		new AOJ1291().run();
	}
}