/** * 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. * This submission gets TLE because the automaton that checks for bordered * words has more states than necessary. * * @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 = HashSet>() var q = LinkedList>() q.offer(Pair(0, 0)) vis.add(Pair(0, 0)) 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)!! val nextPair = Pair(nextState1, nextState2) if (!vis.contains(nextPair)) { vis.add(nextPair) q.offer(nextPair) } } } 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, alphabet.size) } 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`. * * Instead of having 2 * |`word`| + 3 states, the automaton has (|`word`| + 1)(|`word`| + 3) states. * This inefficiency will cause TLE. */ fun buildBorderedAutomaton(word: CharArray, alphabet: CharArray, suffixMatcher: Array): Array { var prefixNodes = Array(word.size + 3) { index-> Node(index, alphabet.size) } var deadIndex = prefixNodes.size - 1 var acceptingIndex = prefixNodes.size - 2 var wordMatchIndex = prefixNodes.size - 3 for (nodeIndex in 0..word.size - 1) { for (char in alphabet) { if (char == word[nodeIndex]) { prefixNodes[nodeIndex].edges.add(Edge(nodeIndex, nodeIndex + 1, char)) } else { prefixNodes[nodeIndex].edges.add(Edge(nodeIndex, deadIndex, char)) } } } for (char in alphabet) { prefixNodes[deadIndex].edges.add(Edge(deadIndex, deadIndex, char)) prefixNodes[wordMatchIndex].edges.add(Edge(wordMatchIndex, acceptingIndex, char)) prefixNodes[acceptingIndex].edges.add(Edge(acceptingIndex, acceptingIndex, char)) } prefixNodes[acceptingIndex].accepting = true var productNodes = Array(suffixMatcher.size * prefixNodes.size) { index -> Node(index, alphabet.size) } for (prefixIndex in 0..prefixNodes.size - 1) { val prefixNode = prefixNodes[prefixIndex] for (suffixIndex in 0..suffixMatcher.size - 1) { val suffixNode = suffixMatcher[suffixIndex] val productIndex1 = prefixNodes.size * suffixIndex + prefixIndex for (alphabetIndex in 0..alphabet.size - 1) { val char = alphabet.get(alphabetIndex) val prefixEdge = prefixNode.edges.get(alphabetIndex) val suffixEdge = suffixNode.edges.get(alphabetIndex) if (prefixEdge.c != char || suffixEdge.c != char) { throw NullPointerException() } val productIndex2 = prefixNodes.size * suffixEdge.v + prefixEdge.v productNodes[productIndex1].edges.add(Edge(productIndex1, productIndex2, char)) } if (prefixNode.accepting && suffixNode.accepting) { productNodes[productIndex1].accepting = true } } } return productNodes } class Node(id: Int, alphabetSize: Int) { val id: Int var accepting: Boolean var edges: ArrayList init { this.id = id this.accepting = false this.edges = ArrayList(alphabetSize) } fun copy(): Node { var copy = Node(id, edges.size) 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)