import java.util.*;
class Node {
boolean valid;
Node[] child;
Node fail;
Node() {
valid = false;
child = new Node[26];
fail = null;
}
void insert(String s, int idx) {
if (idx == s.length()) {
valid = true;
return;
}
int x = s.charAt(idx) - 'a';
if (child[x] == null) {
child[x] = new Node();
}
child[x].insert(s, idx + 1);
}
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
scanner.nextLine(); // Consume newline
Node root = new Node();
for (int i = 0; i < n; i++) {
String s = scanner.nextLine();
root.insert(s, 0);
}
// Build Aho-Corasick automaton
Queue<Node> queue = new LinkedList<>();
queue.add(root);
root.fail = root;
while (!queue.isEmpty()) {
Node current = queue.poll();
for (int i = 0; i < 26; i++) {
Node next = current.child[i];
if (next == null) continue;
if (current == root) {
next.fail = root;
} else {
Node dest = current.fail;
while (dest != root && dest.child[i] == null) {
dest = dest.fail;
}
if (dest.child[i] != null) {
dest = dest.child[i];
}
next.fail = dest;
}
if (next.fail.valid) {
next.valid = true;
}
queue.add(next);
}
}
int m = scanner.nextInt();
scanner.nextLine(); // Consume newline
for (int i = 0; i < m; i++) {
String query = scanner.nextLine();
Node current = root;
boolean found = false;
for (int j = 0; j < query.length(); j++) {
int x = query.charAt(j) - 'a';
while (current != root && current.child[x] == null) {
current = current.fail;
}
if (current.child[x] != null) {
current = current.child[x];
}
if (current.valid) {
found = true;
break;
}
}
System.out.println(found ? "YES" : "NO");
}
scanner.close();
}
}
class Node {
boolean valid; // 이 노드가 집합 S의 문자열 끝인지 여부
Node[] child; // 각 알파벳에 대한 자식 노드
Node fail; // 실패 함수 포인터
Node() {
valid = false;
child = new Node[26];
fail = null;
}
void insert(String s, int idx) {
if (idx == s.length()) {
valid = true; // 문자열의 끝을 표시
return;
}
int x = s.charAt(idx) - 'a';
if (child[x] == null) {
child[x] = new Node();
}
child[x].insert(s, idx + 1); // 다음 문자를 삽입
}
}
// Aho-Corasick 트라이 및 실패 함수 구축
Queue<Node> queue = new LinkedList<>();
queue.add(root);
root.fail = root;
while (!queue.isEmpty()) {
Node current = queue.poll();
for (int i = 0; i < 26; i++) {
Node next = current.child[i];
if (next == null) continue;
if (current == root) {
next.fail = root; // 루트의 자식은 항상 루트를 가리킴
} else {
Node dest = current.fail;
while (dest != root && dest.child[i] == null) {
dest = dest.fail;
}
if (dest.child[i] != null) {
dest = dest.child[i];
}
next.fail = dest;
}
// 실패 노드가 유효한 경우, 이 노드도 유효함
if (next.fail.valid) {
next.valid = true;
}
queue.add(next);
}
}
for (int i = 0; i < m; i++) {
String query = scanner.nextLine();
Node current = root;
boolean found = false;
for (int j = 0; j < query.length(); j++) {
int x = query.charAt(j) - 'a';
while (current != root && current.child[x] == null) {
current = current.fail; // 실패 포인터 따라가기
}
if (current.child[x] != null) {
current = current.child[x]; // 다음 상태로 이동
}
if (current.valid) { // 집합 S의 문자열과 매칭
found = true;
break;
}
}
System.out.println(found ? "YES" : "NO");
}
Aho-Corasick 알고리즘은 여러 패턴 문자열과 쿼리를 동시에 처리하는 효율적인 방법이다. 이 코드는 다음과 같은 점에서 유효하다:
이번 구현을 통해 문자열 탐색 알고리즘의 강력함과 효율성을 다시금 실감할 수 있었다.