1+ import java .io .BufferedInputStream ;
2+ import java .io .EOFException ;
3+ import java .io .IOException ;
4+ import java .io .InputStream ;
5+ import java .util .ArrayList ;
6+ import java .util .HashMap ;
7+ import java .util .HashSet ;
8+ import java .util .Map ;
9+ import java .util .StringTokenizer ;
10+
11+ class FindSystem {
12+
13+ /*
14+ * Принцип работы алгоритма:
15+ * 1) Строим обратный индекс:
16+ * для каждого слова храним список документов, где оно встречается,
17+ * и число его вхождений в каждом документе.
18+ * 2) Для каждого запроса берём только уникальные слова.
19+ * 3) Для каждого такого слова прибавляем его частоту ко всем документам,
20+ * где это слово есть.
21+ * 4) Из найденных документов выбираем 5 лучших:
22+ * сначала по убыванию релевантности, потом по возрастанию номера документа.
23+ *
24+ * Почему алгоритм корректен:
25+ * - В индексе для каждого слова хранится точное число его вхождений в документ.
26+ * - По условию релевантность — это сумма частот всех уникальных слов запроса.
27+ * Именно такую сумму мы и считаем.
28+ * - Повторы слов в запросе не влияют на ответ, потому что мы оставляем только
29+ * уникальные слова запроса.
30+ * - После подсчёта релевантностей выбираем 5 лучших документов по правилу из условия,
31+ * значит ответ получается правильным.
32+ *
33+ * Временная сложность:
34+ * - Построение индекса: O(n), где n — общее число слов во всех документах.
35+ * - Обработка одного запроса: O(n), где n — суммарная длина списков документов
36+ * для уникальных слов запроса.
37+ *
38+ * Пространственная сложность:
39+ * - O(n), где n — число пар (слово, документ) в индексе.
40+ */
41+
42+ private static HashMap <String , ArrayList <int []>> buildIndex (String [] docs ) {
43+ HashMap <String , ArrayList <int []>> index = new HashMap <>();
44+
45+ for (int i = 0 ; i < docs .length ; i ++) {
46+ HashMap <String , Integer > freq = new HashMap <>();
47+ StringTokenizer st = new StringTokenizer (docs [i ]);
48+
49+ while (st .hasMoreTokens ()) {
50+ String word = st .nextToken ();
51+ freq .put (word , freq .getOrDefault (word , 0 ) + 1 );
52+ }
53+
54+ int docId = i + 1 ;
55+ for (Map .Entry <String , Integer > entry : freq .entrySet ()) {
56+ index .computeIfAbsent (entry .getKey (), k -> new ArrayList <>())
57+ .add (new int []{docId , entry .getValue ()});
58+ }
59+ }
60+
61+ return index ;
62+ }
63+
64+ private static String processQuery (String query , HashMap <String , ArrayList <int []>> index ) {
65+ HashSet <String > uniqueWords = new HashSet <>();
66+ StringTokenizer st = new StringTokenizer (query );
67+
68+ while (st .hasMoreTokens ()) {
69+ uniqueWords .add (st .nextToken ());
70+ }
71+
72+ HashMap <Integer , Integer > relevance = new HashMap <>();
73+
74+ for (String word : uniqueWords ) {
75+ ArrayList <int []> docs = index .get (word );
76+ if (docs == null ) {
77+ continue ;
78+ }
79+
80+ for (int [] pair : docs ) {
81+ int docId = pair [0 ];
82+ int count = pair [1 ];
83+ relevance .put (docId , relevance .getOrDefault (docId , 0 ) + count );
84+ }
85+ }
86+
87+ ArrayList <int []> best = new ArrayList <>();
88+
89+ for (Map .Entry <Integer , Integer > entry : relevance .entrySet ()) {
90+ int docId = entry .getKey ();
91+ int score = entry .getValue ();
92+
93+ int pos = 0 ;
94+ while (pos < best .size () && !isBetter (docId , score , best .get (pos )[0 ], best .get (pos )[1 ])) {
95+ pos ++;
96+ }
97+
98+ if (pos < 5 ) {
99+ best .add (pos , new int []{docId , score });
100+ if (best .size () > 5 ) {
101+ best .remove (5 );
102+ }
103+ }
104+ }
105+
106+ StringBuilder sb = new StringBuilder ();
107+ for (int i = 0 ; i < best .size (); i ++) {
108+ if (i > 0 ) {
109+ sb .append (' ' );
110+ }
111+ sb .append (best .get (i )[0 ]);
112+ }
113+
114+ return sb .toString ();
115+ }
116+
117+ private static boolean isBetter (int docId1 , int score1 , int docId2 , int score2 ) {
118+ if (score1 != score2 ) {
119+ return score1 > score2 ;
120+ }
121+ return docId1 < docId2 ;
122+ }
123+
124+ private static void solve () throws Exception {
125+ FastReader reader = new FastReader (System .in );
126+
127+ int n = reader .nextInt ();
128+ String [] docs = new String [n ];
129+ for (int i = 0 ; i < n ; i ++) {
130+ docs [i ] = reader .nextLine ();
131+ }
132+
133+ HashMap <String , ArrayList <int []>> index = buildIndex (docs );
134+
135+ int m = reader .nextInt ();
136+ StringBuilder out = new StringBuilder ();
137+
138+ for (int i = 0 ; i < m ; i ++) {
139+ String query = reader .nextLine ();
140+ out .append (processQuery (query , index )).append ('\n' );
141+ }
142+
143+ System .out .print (out );
144+ }
145+
146+ private static void test () {
147+ String [] docs1 = {
148+ "i love coffee" ,
149+ "coffee with milk and sugar" ,
150+ "free tea for everyone"
151+ };
152+
153+ HashMap <String , ArrayList <int []>> index1 = buildIndex (docs1 );
154+ assertEquals ("1 2" , processQuery ("i like black coffee without milk" , index1 ));
155+ assertEquals ("3" , processQuery ("everyone loves new year" , index1 ));
156+ assertEquals ("2 1" , processQuery ("Mary likes black coffee without milk" , index1 ));
157+
158+ String [] docs2 = {
159+ "buy flat in Moscow" ,
160+ "rent flat in Moscow" ,
161+ "sell flat in Moscow" ,
162+ "want flat in Moscow like crazy" ,
163+ "clean flat in Moscow on weekends" ,
164+ "renovate flat in Moscow"
165+ };
166+
167+ HashMap <String , ArrayList <int []>> index2 = buildIndex (docs2 );
168+ assertEquals ("4 5 1 2 3" , processQuery ("flat in Moscow for crazy weekends" , index2 ));
169+
170+ String [] docs3 = {
171+ "i like dfs and bfs" ,
172+ "i like dfs dfs" ,
173+ "i like bfs with bfs and bfs"
174+ };
175+
176+ HashMap <String , ArrayList <int []>> index3 = buildIndex (docs3 );
177+ assertEquals ("3 1 2" , processQuery ("dfs dfs dfs dfs bfs" , index3 ));
178+ System .out .println ("Test OK" );
179+ }
180+
181+ private static void assertEquals (String expected , String actual ) {
182+ if (!expected .equals (actual )) {
183+ throw new AssertionError ("expected = [" + expected + "], actual = [" + actual + "]" );
184+ }
185+ }
186+
187+ public static void main (String [] args ) throws Exception {
188+ if (System .getProperty ("os.name" ).startsWith ("Windows" )) {
189+ test ();
190+ } else {
191+ solve ();
192+ }
193+ }
194+ private static class FastReader {
195+ private final InputStream in ;
196+ private final byte [] buffer = new byte [1 << 16 ];
197+ private int ptr = 0 ;
198+ private int len = 0 ;
199+
200+ FastReader (InputStream in ) {
201+ this .in = new BufferedInputStream (in );
202+ }
203+
204+ private int read () throws IOException {
205+ if (ptr >= len ) {
206+ len = in .read (buffer );
207+ ptr = 0 ;
208+ if (len <= 0 ) {
209+ return -1 ;
210+ }
211+ }
212+ return buffer [ptr ++];
213+ }
214+
215+ int nextInt () throws IOException {
216+ int c ;
217+ do {
218+ c = read ();
219+ if (c == -1 ) {
220+ throw new EOFException ();
221+ }
222+ } while (c <= ' ' );
223+
224+ int value = 0 ;
225+ while (c > ' ' ) {
226+ value = value * 10 + (c - '0' );
227+ c = read ();
228+ }
229+ return value ;
230+ }
231+
232+ String nextLine () throws IOException {
233+ int c = read ();
234+
235+ while (c == '\n' || c == '\r' ) {
236+ c = read ();
237+ }
238+
239+ StringBuilder sb = new StringBuilder ();
240+ while (c != -1 && c != '\n' && c != '\r' ) {
241+ sb .append ((char ) c );
242+ c = read ();
243+ }
244+
245+ return sb .toString ();
246+ }
247+ }
248+ }
0 commit comments