/** * Let x by Bob's favourite word and let y be Carol's favourite word. * If either "x is a subword of every y-bordered word" or "y is a subword * of every x-bordered word" then it is possible to build an automaton * that accepts exactly the words that have an equal number of occurences * of x and y. * If neither of these conditions are met and occurrences of x and y can * be independent, which makes the language of words with an equal number * of occurrences of x,y a non-regular language. So check for this property * by building an automaton that recognises words that do not have x as a * subword but is a y-bordered word. * * @author Finn Lidbetter */ import java.util.*; val MOD = 1_000_000_007; val ALPHABET_MAX = 26 + 1; val WORD_LEN_MAX = 1000 var ALPHABET_POWERS = LongArray(WORD_LEN_MAX + 2) fun main(args: Array) { ALPHABET_POWERS[0] = 1L for (index in 1..ALPHABET_POWERS.size - 1) { ALPHABET_POWERS[index] = (ALPHABET_POWERS[index - 1] * ALPHABET_MAX) % MOD } val numCases = readLine()!!.toInt() for (testCase in 0 until numCases) { val strings = readLine()!!.split(" ") val alphabet = strings[0].toCharArray() val bobWord = strings[1].toCharArray() val carolWord = strings[2].toCharArray() val bobSuffixMatcher = buildSuffixMatcher(bobWord, alphabet) val bobAntiAutomaton = buildAntiPatternMatcher(bobSuffixMatcher, alphabet) val bobBorderedAutomaton = buildBorderedAutomaton(bobWord, alphabet, bobSuffixMatcher) val carolSuffixMatcher = buildSuffixMatcher(carolWord, alphabet) val carolAntiAutomaton = buildAntiPatternMatcher(carolSuffixMatcher, alphabet) val carolBorderedAutomaton = buildBorderedAutomaton(carolWord, alphabet, carolSuffixMatcher) if ( isProductEmpty(carolBorderedAutomaton, bobAntiAutomaton, alphabet) || isProductEmpty(bobBorderedAutomaton, carolAntiAutomaton, alphabet) ) { println(1) } else { println(0) } } } fun printAutomaton(automaton: Array) { for (nodeIndex in 0..automaton.size-1) { val node = automaton[nodeIndex] println("$nodeIndex" + if (node.accepting) " F" else "") for (edge in node.edges) { val c = edge.c val v = edge.v println("\t$c: $v") } } } /** * Check if the direct product of two automata is empty. */ fun isProductEmpty(automaton1: Array, automaton2: Array, alphabet: CharArray): Boolean { val len1 = automaton1.size val len2 = automaton2.size var vis = Array(len1) { BooleanArray(len2) } var q = LinkedList>() q.offer(Pair(0, 0)) vis[0][0] = true while (q.size > 0) { val (state1, state2) = q.poll()!! if (automaton1[state1].accepting && automaton2[state2].accepting) { return false } var transitionMap1 = HashMap() for (edge in automaton1[state1].edges) { transitionMap1.put(edge.c, edge.v) } var transitionMap2 = HashMap() for (edge in automaton2[state2].edges) { transitionMap2.put(edge.c, edge.v) } for (char in alphabet) { val nextState1 = transitionMap1.get(char)!! val nextState2 = transitionMap2.get(char)!! if (!vis[nextState1][nextState2]) { vis[nextState1][nextState2] = true q.offer(Pair(nextState1, nextState2)) } } } return true } /** * Build an automaton that accepts every word that has `word` as a suffix. */ fun buildSuffixMatcher(word: CharArray, alphabet: CharArray): Array { var prefixHashes = Array(word.size+1) { _ -> 0L } prefixHashes[0] = 0L; for (prefixLen in 1..word.size) { val chIndex = prefixLen - 1 prefixHashes[prefixLen] = ( ALPHABET_MAX * prefixHashes[prefixLen - 1] + (1 + (word[chIndex]-'a')) ) % MOD } // println("prefixHashes: "+Arrays.toString(prefixHashes)) var nodes = Array(word.size + 1) { index -> Node(index) } for (nodeIndex in 0..word.size) { var node = nodes[nodeIndex] for (char in alphabet) { if (nodeIndex < word.size && word[nodeIndex] == char) { node.edges.add(Edge(nodeIndex, nodeIndex + 1, char)) } else { var suffixHash = ( prefixHashes[nodeIndex] * ALPHABET_MAX + (1+(char - 'a')) ) % MOD var suffixLen = nodeIndex + 1 while (suffixLen > word.size || suffixHash != prefixHashes[suffixLen]) { if (suffixLen == 1) { suffixHash = 0 suffixLen--; } else { suffixHash -= ( ALPHABET_POWERS[suffixLen-1] * (1 + (word[nodeIndex+1 - suffixLen] - 'a')) ) suffixHash = suffixHash % MOD if (suffixHash < 0) { suffixHash += MOD } suffixLen-- } } node.edges.add(Edge(nodeIndex, suffixLen, char)) } } node.accepting = false } nodes[nodes.size - 1].accepting = true return nodes } /** * Starting with a suffix matcher, but an automaton that accepts every word that does not * have the suffix matcher's suffix word as a subword. */ fun buildAntiPatternMatcher(suffixMatcher: Array, alphabet: CharArray): Array { var nodes = Array(suffixMatcher.size) { index -> suffixMatcher[index].copy() } val lastIndex = suffixMatcher.size - 1 nodes[lastIndex].edges = ArrayList() for (char in alphabet) { nodes[lastIndex].edges.add(Edge(lastIndex, lastIndex, char)) } for (node in nodes) { node.accepting = true } nodes[lastIndex].accepting = false return nodes } /** * Build an automaton that accepts exactly the words of length at least |`word`|+1 * that start and end with `word`. */ fun buildBorderedAutomaton(word: CharArray, alphabet: CharArray, suffixMatcher: Array): Array { var nodes = Array(2*word.size + 3){ index -> Node(index) } val deadIndex = nodes.size - 1 for (char in alphabet) { nodes[deadIndex].edges.add(Edge(deadIndex, deadIndex, char)) } // Enforce `word` as a prefix. for (nodeIndex in 0..word.size-1) { var node = nodes[nodeIndex] for (char in alphabet) { if (char == word[nodeIndex]) { node.edges.add(Edge(nodeIndex, nodeIndex + 1, char)) } else { node.edges.add(Edge(nodeIndex, deadIndex, char)) } } } // nodes[word.size] means we have read `word` as a prefix. // nodes[word.size+1:word.size+1 + word.size+1] mean we have read // `word` as a prefix and are towards reading `word` as a suffix. val acceptIndex = word.size + 1 + word.size for (nodeIndex in 0..suffixMatcher.size - 1) { val suffixMatcherNode = suffixMatcher[nodeIndex] var node = nodes[word.size+1+nodeIndex] for (edge in suffixMatcherNode.edges) { node.edges.add(Edge(edge.u + word.size + 1, edge.v + word.size + 1, edge.c)) } } nodes[acceptIndex].accepting = true // Now we just need to do the transitions from nodes[word.size]. for (edge in nodes[acceptIndex].edges) { nodes[word.size].edges.add(Edge(word.size, edge.v, edge.c)) } return nodes } class Node(id: Int) { val id: Int var accepting: Boolean var edges: ArrayList init { this.id = id this.accepting = false this.edges = ArrayList() } fun copy(): Node { var copy = Node(id) for (edge in edges) { copy.edges.add(edge) } copy.accepting = accepting return copy } } data class Edge(val u: Int, val v: Int, val c: Char)