diff --git a/pom.xml b/pom.xml
index 7fced37..092333f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,6 @@
21
17
17
- --enable-preview
diff --git a/src/main/java/algorithms/sprint0/AB.java b/src/main/java/algorithms/sprint0/AB.java
new file mode 100644
index 0000000..5258660
--- /dev/null
+++ b/src/main/java/algorithms/sprint0/AB.java
@@ -0,0 +1,74 @@
+package algorithms.sprint0;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.InputMismatchException;
+import java.util.Scanner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class AB {
+ public static void main(String[] args) {
+ Scanner sc = new Scanner(System.in);
+ int a = sc.nextInt();
+ int b = sc.nextInt();
+ long sum = sum(a, b);
+ System.out.println(sum);
+ }
+
+ public static long sum(int a, int b) {
+ return (long) a + b;
+ }
+
+ @Test
+ public void test() {
+ assertEquals(5, sum(2, 3));
+ assertEquals(-1, sum(-2, 1));
+ }
+
+ @ParameterizedTest
+ @CsvSource({
+ "0,0,0",
+ "2,3,5",
+ "-1,4,3",
+ "2147483647,1,2147483648",
+ "2000000000,2000000000,4000000000"
+ })
+ void sum_works(int a, int b, long expected) {
+ assertEquals(expected, AB.sum(a, b));
+ }
+
+ @Test
+ void main_prints_sum_basic() {
+ String in = "2 3\n";
+ InputStream oldIn = System.in;
+ PrintStream oldOut = System.out;
+ try {
+ System.setIn(new ByteArrayInputStream(in.getBytes(StandardCharsets.UTF_8)));
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(buf, true, StandardCharsets.UTF_8));
+
+ AB.main(new String[0]);
+
+ String nl = System.lineSeparator();
+ assertEquals("5" + nl, buf.toString(StandardCharsets.UTF_8));
+ } finally {
+ System.setIn(oldIn);
+ System.setOut(oldOut);
+ }
+ }
+
+ @Test
+ void main_bad_input_throws() {
+ System.setIn(new ByteArrayInputStream("x y\n".getBytes(StandardCharsets.UTF_8)));
+ assertThrows(InputMismatchException.class, () -> AB.main(new String[0]));
+ }
+}
diff --git a/src/main/java/algorithms/sprint0/SlidingAverage.java b/src/main/java/algorithms/sprint0/SlidingAverage.java
new file mode 100644
index 0000000..e5775bd
--- /dev/null
+++ b/src/main/java/algorithms/sprint0/SlidingAverage.java
@@ -0,0 +1,100 @@
+package algorithms.sprint0;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+import static algorithms.sprint0.Utils.readInt;
+import static algorithms.sprint0.Utils.readList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SlidingAverage {
+
+ private static List movingAverage(int n, List arr, int windowSize) {
+ int minSize = Math.min(arr.size(), n);
+ int count = minSize - windowSize + 1;
+ if (windowSize <= 0 || count <= 0) return java.util.Collections.emptyList();
+ List result = new ArrayList<>(count);
+ long sum = 0;
+ for (int i = 0; i < windowSize; i++) sum += arr.get(i);
+ result.add(sum / (double) windowSize);
+ for (int i = windowSize; i < minSize; i++) {
+ sum += arr.get(i) - arr.get(i - windowSize);
+ result.add(sum / (double) windowSize);
+ }
+ return result;
+ }
+
+ public static void main(String[] args) throws IOException {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out))) {
+ int n = readInt(reader);
+ List arr = readList(reader);
+ int windowSize = readInt(reader);
+ List result = movingAverage(n, arr, windowSize);
+ for (double elem : result) {
+ writer.write(elem + " ");
+ }
+ }
+ }
+
+ private static void assertListDoubles(List actual, double... expected) {
+ assertEquals(expected.length, actual.size(), "size");
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals(expected[i], actual.get(i), 1e-9, "idx=" + i);
+ }
+ }
+
+ @Test
+ void test1() {
+ List actual = movingAverage(7, List.of(1, 2, 3, 4, 5, 6, 7), 4);
+ assertListDoubles(actual, 2.5, 3.5, 4.5, 5.5);
+ }
+
+ @Test
+ void w1_returnsOriginalValues() {
+ List actual = movingAverage(7, List.of(1, 2, 3, 4, 5, 6, 7), 1);
+ assertListDoubles(actual, 1, 2, 3, 4, 5, 6, 7);
+ }
+
+ @Test
+ void wEqMin_singleAverage() {
+ List actual = movingAverage(7, List.of(1, 2, 3, 4, 5, 6, 7), 7);
+ assertListDoubles(actual, 4.0);
+ }
+
+ @Test
+ void wGreaterThanMin_empty() {
+ List actual = movingAverage(5, List.of(1, 2, 3, 4, 5, 6, 7), 6);
+ assertEquals(List.of(), actual);
+ }
+
+ @Test
+ void wZeroOrNegative_empty() {
+ assertEquals(List.of(), movingAverage(5, List.of(1, 2, 3, 4, 5), 0));
+ assertEquals(List.of(), movingAverage(5, List.of(1, 2, 3, 4, 5), -3));
+ }
+
+ @Test
+ void cutByN_truncatesAndAverages() {
+ List actual = movingAverage(5, List.of(1, 2, 3, 4, 5, 6, 7), 3);
+ // окна по первым 5 элементам: [1,2,3],[2,3,4],[3,4,5]
+ assertListDoubles(actual, 2.0, 3.0, 4.0);
+ }
+
+ @Test
+ void emptyArray_empty() {
+ List actual = movingAverage(10, List.of(), 3);
+ assertEquals(List.of(), actual);
+ }
+
+ @Test
+ void largeValues_noOverflowInSum() {
+ int M = Integer.MAX_VALUE;
+ List actual = movingAverage(4, List.of(M, M, M, M), 2);
+ // три окна: [M,M],[M,M],[M,M] → среднее = M
+ assertListDoubles(actual, M, M, M);
+ }
+}
diff --git a/src/main/java/algorithms/sprint0/Utils.java b/src/main/java/algorithms/sprint0/Utils.java
new file mode 100644
index 0000000..37b903b
--- /dev/null
+++ b/src/main/java/algorithms/sprint0/Utils.java
@@ -0,0 +1,150 @@
+package algorithms.sprint0;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.junit.jupiter.api.Test;
+
+import java.io.*;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class Utils {
+
+ public static List readList(BufferedReader reader) throws IOException {
+ String line = reader.readLine();
+ if (line == null) throw new NumberFormatException("EOF");
+ return Arrays.stream(line.trim().split("\\s+"))
+ .map(Integer::parseInt)
+ .collect(Collectors.toList());
+ }
+
+ public static void printList(List list, Writer writer) {
+ for (T elem : list) {
+ try {
+ writer.write(String.valueOf(elem));
+ writer.write(" ");
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ public static int readInt(BufferedReader reader) throws IOException {
+ String line = reader.readLine();
+ if (line == null) throw new NumberFormatException("EOF");
+ return Integer.parseInt(line);
+ }
+
+ @Test
+ public void readListbasic() throws Exception {
+ BufferedReader br = new BufferedReader(new StringReader("1 2 3"));
+ List got = readList(br);
+ assertEquals(Arrays.asList(1, 2, 3), got);
+ }
+
+ @Test
+ void readList_mixedWhitespace_and_signs() throws Exception {
+ BufferedReader br = new BufferedReader(new StringReader(" -1\t0 5 "));
+ List got = readList(br);
+ assertEquals(Arrays.asList(-1, 0, 5), got);
+ }
+
+ @Test
+ void readList_emptyLine_throwsNumberFormat() {
+ BufferedReader br = new BufferedReader(new StringReader("\\n"));
+ assertThrows(NumberFormatException.class, () -> readList(br));
+ }
+
+ @Test
+ void readList_EOF_throwsNumberFormat() {
+ BufferedReader br = new BufferedReader(new StringReader(""));
+ assertThrows(NumberFormatException.class, () -> readList(br));
+ }
+
+ @Test
+ void readList_nonIntegerToken_throwsNumberFormat() {
+ BufferedReader br = new BufferedReader(new StringReader("1 a 3"));
+ assertThrows(NumberFormatException.class, () -> readList(br));
+ }
+
+ @Test
+ void readList_overflow_throwsNumberFormat() {
+ BufferedReader br = new BufferedReader(new StringReader("2147483648"));
+ assertThrows(NumberFormatException.class, () -> readList(br));
+ }
+
+ // --- readInt ---
+
+ @Test
+ void readInt_basic() throws Exception {
+ BufferedReader br = new BufferedReader(new StringReader("42"));
+ assertEquals(42, readInt(br));
+ }
+
+ @Test
+ void readInt_negative() throws Exception {
+ BufferedReader br = new BufferedReader(new StringReader("-7"));
+ assertEquals(-7, readInt(br));
+ }
+
+ @Test
+ void readInt_withSpaces_throwsNumberFormat() {
+ BufferedReader br = new BufferedReader(new StringReader(" 42 "));
+ assertThrows(NumberFormatException.class, () -> readInt(br));
+ }
+
+ // --- printList ---
+
+ @Test
+ void printList_integers_trailingSpace_noNewline() throws Exception {
+ StringWriter sw = new StringWriter();
+ printList(Arrays.asList(1, 2, 3), sw);
+ assertEquals("1 2 3 ", sw.toString());
+ }
+
+ @Test
+ void printList_strings_usesToString() throws Exception {
+ StringWriter sw = new StringWriter();
+ printList(Arrays.asList("a", "b"), sw);
+ assertEquals("a b ", sw.toString());
+ }
+
+ @Test
+ void printList_empty_writesNothing() throws Exception {
+ StringWriter sw = new StringWriter();
+ printList(List.of(), sw);
+ assertEquals("", sw.toString());
+ }
+
+ @Test
+ void printList_ioExceptions_areSwallowed() {
+ Writer throwing = new Writer() {
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ throw new IOException("boom");
+ }
+
+ @Override
+ public void flush() {
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public void write(String str) throws IOException {
+ throw new IOException("boom");
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ throw new IOException("boom");
+ }
+ };
+ assertDoesNotThrow(() -> printList(Arrays.asList(1, 2, 3), throwing));
+ }
+}
diff --git a/src/main/java/algorithms/sprint0/Zip.java b/src/main/java/algorithms/sprint0/Zip.java
new file mode 100644
index 0000000..257ef4a
--- /dev/null
+++ b/src/main/java/algorithms/sprint0/Zip.java
@@ -0,0 +1,97 @@
+package algorithms.sprint0;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static algorithms.sprint0.Utils.printList;
+import static algorithms.sprint0.Utils.readList;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class Zip {
+
+ private static List zip(List a, List b, int n) {
+ if (n < 0) throw new IllegalArgumentException("n >= 0 required");
+ int min = Math.min(n, Math.min(a.size(), b.size()));
+ ArrayList integers = new ArrayList<>(min * 2);
+
+ for (int i = 0; i < min; i++) {
+ integers.add(a.get(i));
+ integers.add(b.get(i));
+ }
+ return integers;
+ }
+
+ public static void main(String[] args) throws IOException {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out))) {
+ int n = Integer.parseInt(reader.readLine().trim());
+ List a = readList(reader);
+ List b = readList(reader);
+ printList(zip(a, b, n), writer);
+ }
+ }
+
+ @Test
+ void equalLength_nEqualsSize() {
+ List a = Arrays.asList(1, 3, 5);
+ List b = Arrays.asList(2, 4, 6);
+ assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), zip(a, b, 3));
+ }
+
+ @Test
+ void nSmallerThanSizes() {
+ List a = Arrays.asList(10, 30, 50);
+ List b = Arrays.asList(20, 40, 60);
+ assertEquals(Arrays.asList(10, 20, 30, 40), zip(a, b, 2));
+ }
+
+ @Test
+ void nZero_returnsEmpty() {
+ List a = Arrays.asList(1, 3, 5);
+ List b = Arrays.asList(2, 4, 6);
+ assertTrue(zip(a, b, 0).isEmpty());
+ }
+
+ @Test
+ void nGreaterThanSizes_clampedToMin() {
+ List a = Arrays.asList(1, 3, 5);
+ List b = Arrays.asList(2, 4, 6);
+ assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6), zip(a, b, 10));
+ }
+
+ @Test
+ void oneListShorter_minByShorter() {
+ List a = List.of(1);
+ List b = Arrays.asList(2, 4, 6);
+ assertEquals(Arrays.asList(1, 2), zip(a, b, 3));
+ }
+
+ @Test
+ void emptyLists_anyN_returnsEmpty() {
+ List a = List.of();
+ List b = List.of();
+ assertTrue(zip(a, b, 5).isEmpty());
+ }
+
+ @Test
+ void negativeN_throwsIAE_withMessage() {
+ IllegalArgumentException ex =
+ assertThrows(IllegalArgumentException.class,
+ () -> zip(List.of(1), List.of(2), -1));
+ assertTrue(ex.getMessage().contains("n >= 0"));
+ }
+
+ @Test
+ void nullA_throwsNPE() {
+ assertThrows(NullPointerException.class, () -> zip(null, List.of(2), 1));
+ }
+
+ @Test
+ void nullB_throwsNPE() {
+ assertThrows(NullPointerException.class, () -> zip(List.of(1), null, 1));
+ }
+}
diff --git a/src/main/java/algorithms/sprint1/Distances.java b/src/main/java/algorithms/sprint1/Distances.java
new file mode 100644
index 0000000..5392669
--- /dev/null
+++ b/src/main/java/algorithms/sprint1/Distances.java
@@ -0,0 +1,160 @@
+import java.io.*;
+import java.util.Arrays;
+
+// https://contest.yandex.ru/contest/22450/run-report/157289807/
+public class Distances {
+
+ // -------------------- SOLUTION --------------------
+ static int[] solve(int[] a) {
+ int n = a.length;
+ int[] dist = new int[n];
+
+ int lastZero = -n;
+ for (int i = 0; i < n; i++) {
+ if (a[i] == 0) {
+ lastZero = i;
+ }
+ dist[i] = i - lastZero;
+ }
+
+ int nextZero = lastZero;
+ for (int i = lastZero; i >= 0; i--) {
+ if (a[i] == 0) nextZero = i;
+ dist[i] = Math.min(dist[i], nextZero - i);
+ }
+ return dist;
+ }
+
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0, len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+ if (len <= 0) return -1;
+ }
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) throw new EOFException("Unexpected EOF");
+ } while (c <= ' ');
+
+ int sign = 1;
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+ return val * sign;
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+ private final byte[] tmp = new byte[12];
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) flush();
+ buf[p++] = (byte) b;
+ }
+
+ void writeInt(int x) throws IOException {
+ if (x == 0) {
+ writeByte('0');
+ return;
+ }
+ if (x < 0) {
+ writeByte('-');
+ x = -x;
+ }
+
+ int k = 0;
+ while (x > 0) {
+ tmp[k++] = (byte) ('0' + (x % 10));
+ x /= 10;
+ }
+ for (int i = k - 1; i >= 0; i--) writeByte(tmp[i]);
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+ // -------------------- INPUT / OUTPUT --------------------
+ static void run() {
+
+ try (InputStream input = new BufferedInputStream(new FileInputStream("input.txt")); OutputStream output = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
+
+
+ FastIn in = new FastIn(input);
+ FastOut out = new FastOut(output);
+
+ int n = in.nextInt();
+ int[] a = new int[n];
+ for (int i = 0; i < n; i++) a[i] = in.nextInt();
+
+ int[] dist = solve(a);
+
+ for (int i = 0; i < n; i++) {
+ if (i > 0) out.writeByte(' ');
+ out.writeInt(dist[i]);
+ }
+ out.writeByte('\n');
+ out.flush();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // -------------------- TESTS --------------------
+ static void assertEq(int[] exp, int[] act, String name) {
+ if (!Arrays.equals(exp, act)) {
+ throw new AssertionError(name + "\nexp=" + Arrays.toString(exp) + "\nact=" + Arrays.toString(act));
+ }
+ }
+
+ static void test() {
+ assertEq(new int[]{0, 1, 2, 1, 0}, solve(new int[]{0, 1, 4, 9, 0}), "sample1");
+ assertEq(new int[]{0, 1, 2, 3, 4, 5}, solve(new int[]{0, 7, 9, 4, 8, 20}), "sample2");
+ assertEq(new int[]{0}, solve(new int[]{0}), "n=1");
+ assertEq(new int[]{0, 0, 0}, solve(new int[]{0, 0, 0}), "all zeros");
+ assertEq(new int[]{2, 1, 0, 1, 2}, solve(new int[]{5, 6, 0, 7, 8}), "zero in middle");
+ System.out.println("OK");
+ }
+
+ // -------------------- MAIN --------------------
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}
diff --git a/src/main/java/algorithms/sprint1/SleightOfHand.java b/src/main/java/algorithms/sprint1/SleightOfHand.java
new file mode 100644
index 0000000..087bdb4
--- /dev/null
+++ b/src/main/java/algorithms/sprint1/SleightOfHand.java
@@ -0,0 +1,237 @@
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+// https://contest.yandex.ru/contest/22450/run-report/157017632/
+public class SleightOfHand {
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0, len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+ if (len <= 0) return -1;
+ }
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) throw new EOFException("Unexpected EOF");
+ } while (c <= ' ');
+
+ int sign = 1;
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+ return val * sign;
+ }
+
+ String next() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) throw new EOFException("Unexpected EOF");
+ } while (c <= ' ');
+
+ byte[] tmp = new byte[32];
+ int n = 0;
+ while (c > ' ') {
+ if (n == tmp.length) {
+ byte[] t2 = new byte[tmp.length * 2];
+ System.arraycopy(tmp, 0, t2, 0, tmp.length);
+ tmp = t2;
+ }
+ tmp[n++] = (byte) c;
+ c = read();
+ if (c == -1) break;
+ }
+ return new String(tmp, 0, n);
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+ private final byte[] tmp = new byte[12];
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) flush();
+ buf[p++] = (byte) b;
+ }
+
+ void writeInt(int x) throws IOException {
+ if (x == 0) {
+ writeByte('0');
+ return;
+ }
+ if (x < 0) {
+ writeByte('-');
+ x = -x;
+ }
+
+ int k = 0;
+ while (x > 0) {
+ tmp[k++] = (byte) ('0' + (x % 10));
+ x /= 10;
+ }
+ for (int i = k - 1; i >= 0; i--) writeByte(tmp[i]);
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ int k = in.nextInt();
+ int limit = 2 * k;
+ int[] count = new int[10];
+
+ for (int r = 0; r < 4; r++) {
+ String s = in.next();
+ // На всякий случай, если токенайзер разделит строку (обычно не будет)
+ while (s.length() < 4) {
+ s += in.next();
+ }
+ for (int c = 0; c < 4; c++) {
+ char ch = s.charAt(c);
+ if (ch != '.') {
+ count[ch - '0']++;
+ }
+ }
+ }
+
+ int points = 0;
+ for (int d = 1; d <= 9; d++) {
+ int cnt = count[d];
+ if (cnt != 0 && cnt <= limit) {
+ points++;
+ }
+ }
+
+ out.writeInt(points);
+ out.writeByte('\n');
+ out.flush();
+ }
+
+ private static void test() {
+ // Примеры из условия
+ assertEq(2, solve(3, new int[][]{
+ {1, 2, 3, 1},
+ {2, 0, 0, 2},
+ {2, 0, 0, 2},
+ {2, 0, 0, 2}
+ }));
+
+ assertEq(1, solve(4, new int[][]{
+ {1, 1, 1, 1},
+ {9, 9, 9, 9},
+ {1, 1, 1, 1},
+ {9, 9, 1, 1}
+ }));
+
+ assertEq(0, solve(1, new int[][]{
+ {1, 1, 1, 1},
+ {1, 1, 1, 1},
+ {1, 1, 1, 1},
+ {1, 1, 1, 1}
+ }));
+
+ // Критичный тест: ровно 2*k нажатий — очко должно засчитаться
+ assertEq(1, solve(2, new int[][]{
+ {5, 5, 0, 0},
+ {0, 0, 0, 0},
+ {0, 0, 0, 0},
+ {5, 5, 0, 0}
+ }));
+
+ // Чуть больше лимита — очко не должно засчитаться
+ assertEq(0, solve(2, new int[][]{
+ {6, 6, 6, 0},
+ {6, 6, 0, 0},
+ {0, 0, 0, 0},
+ {0, 0, 0, 0}
+ })); // 6 встречается 5 раз, limit=4
+
+ // Пустое поле
+ assertEq(0, solve(5, new int[][]{
+ {0, 0, 0, 0},
+ {0, 0, 0, 0},
+ {0, 0, 0, 0},
+ {0, 0, 0, 0}
+ }));
+
+ // Несколько цифр в пределах лимита
+ assertEq(2, solve(1, new int[][]{
+ {1, 1, 2, 0},
+ {0, 0, 0, 0},
+ {0, 0, 0, 0},
+ {0, 0, 0, 0}
+ })); // limit=2, cnt(1)=2, cnt(2)=1
+
+ System.out.println("Test OK");
+ }
+
+ static int solve(int k, int[][] a) {
+ int points = 0;
+ final int limit = 2 * k;
+ final int[] count = new int[10];
+
+ for (int[] row : a) {
+ for (int v : row) {
+ if (v != 0) count[v]++;
+ }
+ }
+
+ for (int d = 1; d <= 9; d++) {
+ int cnt = count[d];
+ if (cnt != 0 && cnt <= limit) {
+ points++;
+ }
+ }
+ return points;
+ }
+
+ static void assertEq(int exp, int act) {
+ if (exp != act) {
+ throw new AssertionError("Expected=" + exp + ", actual=" + act);
+ }
+ }
+}
diff --git a/src/main/java/algorithms/sprint1/Solution2.java b/src/main/java/algorithms/sprint1/Solution2.java
new file mode 100644
index 0000000..dcf5542
--- /dev/null
+++ b/src/main/java/algorithms/sprint1/Solution2.java
@@ -0,0 +1,55 @@
+package algorithms.sprint1;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.StringTokenizer;
+
+//
+class Node {
+ public V value;
+ public Node next;
+
+ public Node(V value, Node next) {
+ this.value = value;
+ this.next = next;
+ }
+}
+//
+
+public class Solution2 {
+ public static void solution(Node head) {
+ // Your code
+ // ヽ(´▽`)/
+ }
+
+ private static void test() {
+ Node node3 = new Node<>("node3", null);
+ Node node2 = new Node<>("node2", node3);
+ Node node1 = new Node<>("node1", node2);
+ Node node0 = new Node<>("node0", node1);
+ solution(node0);
+ /*
+ Output is:
+ node0
+ node1
+ node2
+ node3
+ */
+ }
+
+ public static void main(String[] args) throws IOException {
+ StringBuilder output_buffer = new StringBuilder();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ int num_lines = Integer.parseInt(reader.readLine());
+ for (int i = 0; i < num_lines; ++i) {
+ StringTokenizer tokenizer = new StringTokenizer(reader.readLine());
+ int value_1 = Integer.parseInt(tokenizer.nextToken());
+ int value_2 = Integer.parseInt(tokenizer.nextToken());
+ int result = value_1 + value_2;
+ output_buffer.append(result).append("\n");
+ }
+ System.out.println(output_buffer.toString());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/algorithms/sprint2/Calculator.java b/src/main/java/algorithms/sprint2/Calculator.java
new file mode 100644
index 0000000..58e955a
--- /dev/null
+++ b/src/main/java/algorithms/sprint2/Calculator.java
@@ -0,0 +1,205 @@
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/*
+Принцип:
+- Читаем токены ОПН слева направо.
+- Если считанный символ - Число -> push в стек.
+- Если считанный символ - Математическая Операция (+-*\) -> pop b, pop a, считаем a op b, push результат.
+- Ответ: верх стека.
+
+Корректность:
+- В ОПН операция применяется к двум ближайшим слева операндам; к моменту чтения операции они лежат
+ на вершине стека. Мы заменяем эти два значения на результат, сохраняя корректное состояние.
+- Деление требуется “вниз” (floor), поэтому используем Math.floorDiv(a, b).
+
+Сложность:
+- Время O(m), m — число токенов.
+- Память - в худшем O(m)
+*/
+
+
+public class Calculator {
+
+ private static int eval(FastIn in) throws IOException {
+ Deque st = new ArrayDeque<>();
+
+ while (true) {
+ String t;
+ try {
+ t = in.next();
+ } catch (EOFException e) {
+ break;
+ }
+
+ if (t.length() == 1) {
+ char op = t.charAt(0);
+ if (op == '+' || op == '-' || op == '*' || op == '/') {
+ int b = st.pop();
+ int a = st.pop();
+
+ int r;
+ if (op == '+') {
+ r = a + b;
+ } else if (op == '-') {
+ r = a - b;
+ } else if (op == '*') {
+ r = a * b;
+ } else {
+ r = Math.floorDiv(a, b);
+ }
+
+ st.push(r);
+ continue;
+ }
+ }
+
+ st.push(Integer.parseInt(t));
+ }
+
+ return st.peek();
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ int ans = eval(in);
+
+ out.writeInt(ans);
+ out.writeByte('\n');
+ out.flush();
+ }
+
+ // -------------------- LOCAL TESTS --------------------
+ private static void test() throws Exception {
+ assertEq(9, evalFromString("2 1 + 3 *"));
+ assertEq(38, evalFromString("7 2 + 4 * 2 +"));
+ assertEq(-1, evalFromString("-1 3 /"));
+ assertEq(-2, evalFromString("-4 3 /"));
+ assertEq(2, evalFromString("10 2 4 * -"));
+ assertEq(5, evalFromString("5"));
+ System.out.println("Test OK");
+ }
+
+ private static int evalFromString(String s) throws Exception {
+ InputStream is = new ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII));
+ return eval(new FastIn(is));
+ }
+
+ private static void assertEq(int exp, int act) {
+ if (exp != act) {
+ throw new AssertionError("Expected=" + exp + ", actual=" + act);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+ if (len <= 0) {
+ return -1;
+ }
+ }
+ return buf[ptr++];
+ }
+
+ String next() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("EOF");
+ }
+ } while (c <= ' ');
+
+ byte[] tmp = new byte[32];
+ int n = 0;
+
+ while (c > ' ') {
+ if (n == tmp.length) {
+ byte[] t2 = new byte[tmp.length << 1];
+ System.arraycopy(tmp, 0, t2, 0, tmp.length);
+ tmp = t2;
+ }
+ tmp[n++] = (byte) c;
+
+ c = read();
+ if (c == -1) {
+ break;
+ }
+ }
+
+ return new String(tmp, 0, n);
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+ private final byte[] tmp = new byte[12];
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) {
+ flush();
+ }
+ buf[p++] = (byte) b;
+ }
+
+ void writeInt(int x) throws IOException {
+ if (x == 0) {
+ writeByte('0');
+ return;
+ }
+ if (x < 0) {
+ writeByte('-');
+ x = -x;
+ }
+
+ int k = 0;
+ while (x > 0) {
+ tmp[k++] = (byte) ('0' + (x % 10));
+ x /= 10;
+ }
+ for (int i = k - 1; i >= 0; i--) {
+ writeByte(tmp[i]);
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+}
diff --git a/src/main/java/algorithms/sprint2/Deque.java b/src/main/java/algorithms/sprint2/Deque.java
new file mode 100644
index 0000000..3262fa8
--- /dev/null
+++ b/src/main/java/algorithms/sprint2/Deque.java
@@ -0,0 +1,387 @@
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/*
+Принцип работы алгоритма
+Используется дек фиксированного размера m, реализованный на массиве как кольцевой буфер.
+Храним:
+- a[] — массив ёмкости m;
+- head — индекс первого элемента дека;
+- tail — индекс позиции "после последнего" элемента дека;
+- size — текущее количество элементов.
+
+Индексы head и tail двигаются по кругу: при выходе за границу массива переходят в начало/конец.
+Операции:
+- push_back(x): записать x в a[tail], сдвинуть tail вперёд по кругу, увеличить size.
+- push_front(x): сдвинуть head назад по кругу, записать x в a[head], увеличить size.
+- pop_front(): взять a[head], сдвинуть head вперёд, уменьшить size.
+- pop_back(): сдвинуть tail назад, взять a[tail], уменьшить size.
+
+Корректность (почему работает)
+Поддерживаются инварианты:
+1) size всегда в диапазоне [0..m].
+2) head указывает на первый элемент (если size > 0).
+3) tail указывает на позицию сразу после последнего элемента (если size > 0).
+4) Элементы дека в порядке обхода лежат в a по циклу, начиная с head и длиной size.
+
+Проверка по операциям:
+- push_back при size < m кладёт новый элемент ровно в позицию tail, затем tail сдвигается на следующую
+ позицию по кругу, поэтому tail снова указывает на "после последнего", а порядок элементов сохраняется.
+- push_front при size < m сначала сдвигает head на предыдущую позицию по кругу и кладёт туда новый элемент,
+ поэтому head начинает указывать на добавленный элемент (он становится первым), а порядок сохраняется.
+- pop_front при size > 0 читает первый элемент из a[head], затем сдвигает head вперёд, уменьшая size:
+ новым первым становится следующий элемент, инварианты сохраняются.
+- pop_back при size > 0 сначала сдвигает tail назад (на позицию последнего элемента), читает его и уменьшает size:
+ tail снова становится "после последнего", инварианты сохраняются.
+
+Ошибки "error" выводятся только когда операция невозможна:
+- push_* при size == m (переполнение),
+- pop_* при size == 0 (пустой дек).
+В этих случаях состояние (head, tail, size) не меняется, что сохраняет инварианты.
+
+Сложность
+Время: O(n), где n - количество операций. Каждая операция дека выполняется за O(1)
+Память: массив из m элементов и несколько целых переменных: O(m) по памяти.
+*/
+
+
+public class Deque {
+
+ // -------------------- RING BUFFER DEQUE --------------------
+ static final class RingDeque {
+ private final int[] a;
+ private final int cap;
+ private int head = 0; // индекс первого элемента
+ private int tail = 0; // индекс позиции "после последнего"
+ private int size = 0;
+
+ RingDeque(int cap) {
+ this.cap = cap;
+ this.a = new int[cap];
+ }
+
+ private int next(int i) {
+ return (i+1) % cap;
+ }
+
+ private int prev(int i) {
+ return (i-1+cap) % cap;
+ }
+
+ boolean isEmpty() {
+ return size == 0;
+ }
+
+ boolean isFull() {
+ return size == cap;
+ }
+
+ void pushBack(int x) {
+ a[tail] = x;
+ tail = next(tail);
+ size++;
+ }
+
+ void pushFront(int x) {
+ head = prev(head);
+ a[head] = x;
+ size++;
+ }
+
+ int popFront() {
+ int x = a[head];
+ head = next(head);
+ size--;
+ return x;
+ }
+
+ int popBack() {
+ tail = prev(tail);
+ int x = a[tail];
+ size--;
+ return x;
+ }
+ }
+
+ private static void process(FastIn in, FastOut out) throws Exception {
+ int n = in.nextInt();
+ int m = in.nextInt();
+
+ RingDeque dq = new RingDeque(m);
+
+ for (int i = 0; i < n; i++) {
+ String cmd = in.next();
+
+ switch (cmd) {
+ case "push_back" -> {
+ int x = in.nextInt();
+ if (dq.isFull()) {
+ out.writeStr("error\n");
+ } else {
+ dq.pushBack(x);
+ }
+ }
+ case "push_front" -> {
+ int x = in.nextInt();
+ if (dq.isFull()) {
+ out.writeStr("error\n");
+ } else {
+ dq.pushFront(x);
+ }
+ }
+ case "pop_front" -> {
+ if (dq.isEmpty()) {
+ out.writeStr("error\n");
+ } else {
+ out.writeInt(dq.popFront());
+ out.writeByte('\n');
+ }
+ }
+ case "pop_back" -> {
+ if (dq.isEmpty()) {
+ out.writeStr("error\n");
+ } else {
+ out.writeInt(dq.popBack());
+ out.writeByte('\n');
+ }
+ }
+ }
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+ process(in, out);
+ out.flush();
+ }
+
+ // -------------------- TESTS --------------------
+ private static String solveIO(String input) throws Exception {
+ ByteArrayInputStream bin = new ByteArrayInputStream(input.getBytes());
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ FastIn in = new FastIn(bin);
+ FastOut out = new FastOut(bout);
+ process(in, out);
+ out.flush();
+ return bout.toString();
+ }
+
+ private static void test() throws Exception {
+ // Пример 1
+ assertEq(
+ "861\n-819\n",
+ solveIO(
+ "4\n" +
+ "4\n" +
+ "push_front 861\n" +
+ "push_front -819\n" +
+ "pop_back\n" +
+ "pop_back\n"
+ )
+ );
+
+ // Пример 2
+ assertEq(
+ "-855\n0\n844\n",
+ solveIO(
+ "7\n" +
+ "10\n" +
+ "push_front -855\n" +
+ "push_front 0\n" +
+ "pop_back\n" +
+ "pop_back\n" +
+ "push_back 844\n" +
+ "pop_back\n" +
+ "push_back 823\n"
+ )
+ );
+
+ // Пример 3
+ assertEq(
+ "20\n102\n",
+ solveIO(
+ "6\n" +
+ "6\n" +
+ "push_front -201\n" +
+ "push_back 959\n" +
+ "push_back 102\n" +
+ "push_front 20\n" +
+ "pop_front\n" +
+ "pop_back\n"
+ )
+ );
+
+ // Емкость 1 + переполнение + попытка pop из пустого
+ assertEq(
+ "error\n1\nerror\n",
+ solveIO(
+ "4\n" +
+ "1\n" +
+ "push_front 1\n" +
+ "push_back 2\n" +
+ "pop_back\n" +
+ "pop_front\n"
+ )
+ );
+
+ // Wrap-around: head/tail должны корректно "перепрыгивать" границу массива
+ assertEq(
+ "1\n4\n2\n3\n",
+ solveIO(
+ "8\n" +
+ "3\n" +
+ "push_back 1\n" +
+ "push_back 2\n" +
+ "push_back 3\n" +
+ "pop_front\n" +
+ "push_back 4\n" +
+ "pop_back\n" +
+ "pop_front\n" +
+ "pop_front\n"
+ )
+ );
+
+ System.out.println("Test OK");
+ }
+
+ static void assertEq(String exp, String act) {
+ if (!exp.equals(act)) {
+ throw new AssertionError("Expected:\n" + exp + "\nActual:\n" + act);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0, len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+ if (len <= 0) {
+ return -1;
+ }
+ }
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ int sign = 1;
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+ return val * sign;
+ }
+
+ String next() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ byte[] tmp = new byte[32];
+ int n = 0;
+ while (c > ' ') {
+ if (n == tmp.length) {
+ byte[] t2 = new byte[tmp.length * 2];
+ System.arraycopy(tmp, 0, t2, 0, tmp.length);
+ tmp = t2;
+ }
+ tmp[n++] = (byte) c;
+ c = read();
+ if (c == -1) {
+ break;
+ }
+ }
+ return new String(tmp, 0, n);
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+ private final byte[] tmp = new byte[12];
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) {
+ flush();
+ }
+ buf[p++] = (byte) b;
+ }
+
+ void writeStr(String s) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ writeByte(s.charAt(i));
+ }
+ }
+
+ void writeInt(int x) throws IOException {
+ if (x == 0) {
+ writeByte('0');
+ return;
+ }
+ if (x < 0) {
+ writeByte('-');
+ x = -x;
+ }
+
+ int k = 0;
+ while (x > 0) {
+ tmp[k++] = (byte) ('0' + (x % 10));
+ x /= 10;
+ }
+ for (int i = k - 1; i >= 0; i--) {
+ writeByte(tmp[i]);
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+}
diff --git a/src/main/java/algorithms/sprint3/BrokenArray.java b/src/main/java/algorithms/sprint3/BrokenArray.java
new file mode 100644
index 0000000..2177add
--- /dev/null
+++ b/src/main/java/algorithms/sprint3/BrokenArray.java
@@ -0,0 +1,79 @@
+// https://contest.yandex.ru/contest/23815/run-report/159759692/
+
+public class BrokenArray {
+
+ /*
+ * Принцип работы алгоритма:
+ * Используем модифицированный бинарный поиск.
+ * На каждом шаге рассматриваем середину отрезка [left, right].
+ * Хотя массив "сломанный" (циклически сдвинутый), хотя бы одна из половин
+ * [left, mid] или [mid, right] обязательно отсортирована по возрастанию.
+ * Определяем, какая именно половина отсортирована, и проверяем, может ли
+ * искомый элемент k лежать внутри неё. Если да — продолжаем поиск в этой
+ * половине, иначе — в другой.
+ *
+ * Почему алгоритм корректен:
+ * 1) В массиве все элементы уникальны, значит для любой середины можно
+ * однозначно понять, какая половина отсортирована.
+ * 2) Если левая половина отсортирована и k лежит в её диапазоне значений,
+ * то k может находиться только в ней; правую половину можно отбросить.
+ * Аналогично для правой половины.
+ * 3) На каждом шаге размер рассматриваемого отрезка строго уменьшается,
+ * поэтому алгоритм завершится.
+ * 4) Если k есть в массиве, мы не отбросим отрезок, где он расположен,
+ * значит рано или поздно попадём в его индекс. Если k нет, указатели
+ * пересекутся, и мы корректно вернём -1.
+ *
+ * Временная сложность: O(log n), где n — количество элементов в массиве
+ * (n = arr.length), так как на каждом шаге отбрасывается примерно
+ * половина массива.
+ * Пространственная сложность: O(1).
+ */
+ public static int brokenSearch(int[] arr, int k) {
+ int left = 0;
+ int right = arr.length - 1;
+
+ while (left <= right) {
+ int mid = left + (right - left) / 2;
+
+ if (arr[mid] == k) {
+ return mid;
+ }
+
+ if (arr[left] <= arr[mid]) {
+ if (arr[left] <= k && k < arr[mid]) {
+ right = mid - 1;
+ } else {
+ left = mid + 1;
+ }
+ } else {
+ if (arr[mid] < k && k <= arr[right]) {
+ left = mid + 1;
+ } else {
+ right = mid - 1;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ private static void test() {
+ int[] arr1 = {19, 21, 100, 101, 1, 4, 5, 7, 12};
+ assert 6 == brokenSearch(arr1, 5);
+
+ int[] arr2 = {5, 1};
+ assert 1 == brokenSearch(arr2, 1);
+
+ int[] arr3 = {1, 2, 3, 4, 5, 6};
+ assert 3 == brokenSearch(arr3, 4);
+
+ int[] arr4 = {4, 5, 6, 7, 0, 1, 2};
+ assert 4 == brokenSearch(arr4, 0);
+ assert -1 == brokenSearch(arr4, 3);
+
+ int[] arr5 = {1};
+ assert 0 == brokenSearch(arr5, 1);
+ assert -1 == brokenSearch(arr5, 2);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/algorithms/sprint3/FastSort.java b/src/main/java/algorithms/sprint3/FastSort.java
new file mode 100644
index 0000000..fb5c266
--- /dev/null
+++ b/src/main/java/algorithms/sprint3/FastSort.java
@@ -0,0 +1,212 @@
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+//https://contest.yandex.ru/contest/23815/run-report/160042811/
+
+public class FastSort {
+
+ /*
+ Принцип работы алгоритма:
+ 1) Считываем n участников в массив.
+ 2) Сортируем массив in-place быстрой сортировкой.
+ В качестве опорного элемента (pivot) берём участника из середины текущего отрезка.
+ Далее двумя указателями:
+ - указатель i двигаем слева направо, пока a[i] должен стоять левее pivot;
+ - указатель j двигаем справа налево, пока a[j] должен стоять правее pivot.
+ Когда находятся два элемента не на своих сторонах, меняем их местами.
+ После завершения разбиения рекурсивно сортируем меньшую из двух частей,
+ а большую продолжаем обрабатывать в цикле. Это уменьшает глубину стека.
+ 3) Определяем лучше участника методом boolean better(a, b) по правилам задачи:
+ - у A решено больше задач;
+ - при равенстве задач у A меньше штраф;
+ - при равенстве штрафа логин A меньше лексикографически.
+
+ Почему алгоритм корректен:
+ 1) Функция better(a, b) задаёт ровно тот порядок, который требуется в условии.
+ 2) На каждом шаге quicksort мы делим массив относительно pivot:
+ слева остаются элементы, которые должны идти не позже pivot,
+ справа — элементы, которые должны идти не раньше pivot.
+ 3) После этого рекурсивно сортируем обе части.
+ 4) База рекурсии — отрезок длины 0 или 1, он уже отсортирован.
+ Значит, весь массив будет отсортирован правильно.
+
+ Временная сложность: в среднем O(n log n), в худшем случае O(n^2),
+ где n количество участников
+
+ Пространственная сложность:
+ - O(1) дополнительной памяти на перестановки;
+ - O(log n) на стек вызовов даже в худшем случае,
+ потому что рекурсивный вызов делается только для меньшей части массива.
+ Размер подмассива в следующем рекурсивном вызове не больше половины текущего,
+ поэтому глубина рекурсии не превосходит O(log n).
+ Большая часть обрабатывается итеративно в том же вызове функции.
+ */
+
+ static class Participant {
+ String login;
+ int solved;
+ int penalty;
+
+ Participant(String login, int solved, int penalty) {
+ this.login = login;
+ this.solved = solved;
+ this.penalty = penalty;
+ }
+ }
+
+ static boolean better(Participant a, Participant b) {
+ if (a.solved != b.solved) {
+ return a.solved > b.solved;
+ }
+ if (a.penalty != b.penalty) {
+ return a.penalty < b.penalty;
+ }
+ return a.login.compareTo(b.login) < 0;
+ }
+
+ static void swap(Participant[] a, int i, int j) {
+ Participant temp = a[i];
+ a[i] = a[j];
+ a[j] = temp;
+ }
+
+ static void quickSort(Participant[] a, int left, int right) {
+ while (left < right) {
+ int i = left;
+ int j = right;
+ Participant pivot = a[left + (right - left) / 2];
+
+ while (i <= j) {
+ while (better(a[i], pivot)) {
+ i++;
+ }
+ while (better(pivot, a[j])) {
+ j--;
+ }
+ if (i <= j) {
+ swap(a, i, j);
+ i++;
+ j--;
+ }
+ }
+
+ if (j - left < right - i) {
+ if (left < j) {
+ quickSort(a, left, j);
+ }
+ left = i;
+ } else {
+ if (i < right) {
+ quickSort(a, i, right);
+ }
+ right = j;
+ }
+ }
+ }
+
+ static String solve(Participant[] participants) {
+ if (participants.length > 1) {
+ quickSort(participants, 0, participants.length - 1);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (Participant p : participants) {
+ sb.append(p.login).append('\n');
+ }
+ return sb.toString();
+ }
+
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buffer = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buffer);
+ ptr = 0;
+ if (len <= 0) {
+ return -1;
+ }
+ }
+ return buffer[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException();
+ }
+ } while (c <= ' ');
+
+ int value = 0;
+ while (c > ' ') {
+ value = value * 10 + (c - '0');
+ c = read();
+ }
+ return value;
+ }
+
+ String next() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException();
+ }
+ } while (c <= ' ');
+
+ StringBuilder sb = new StringBuilder();
+ while (c > ' ') {
+ sb.append((char) c);
+ c = read();
+ }
+ return sb.toString();
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+
+ int n = in.nextInt(); // n — количество участников
+ Participant[] participants = new Participant[n];
+
+ for (int i = 0; i < n; i++) {
+ String login = in.next();
+ int solved = in.nextInt();
+ int penalty = in.nextInt();
+ participants[i] = new Participant(login, solved, penalty);
+ }
+
+ System.out.print(solve(participants));
+ }
+
+ private static void test() {
+ Participant[] a = {new Participant("alla", 4, 100), new Participant("gena", 6, 1000), new Participant("gosha", 2, 90), new Participant("rita", 2, 90), new Participant("timofey", 4, 80)};
+
+ String expected = "gena\ntimofey\nalla\ngosha\nrita\n";
+ String actual = solve(a);
+
+ if (!expected.equals(actual)) {
+ throw new AssertionError("Expected:\n" + expected + "Actual:\n" + actual);
+ }
+
+ System.out.println("Test OK");
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/algorithms/sprint4/FindSystem.java b/src/main/java/algorithms/sprint4/FindSystem.java
new file mode 100644
index 0000000..8864bcb
--- /dev/null
+++ b/src/main/java/algorithms/sprint4/FindSystem.java
@@ -0,0 +1,263 @@
+import java.io.BufferedInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+// https://contest.yandex.ru/contest/24414/run-report/160043341/
+
+class FindSystem {
+
+ /*
+ * Принцип работы алгоритма:
+ * 1) Строим обратный индекс:
+ * для каждого слова храним список документов, где оно встречается,
+ * и число его вхождений в каждом документе.
+ * 2) Для каждого запроса берём только уникальные слова из этого запроса,
+ * то есть если слово в запросе встретилось несколько раз, учитываем его один раз.
+ * 3) Для каждого такого слова прибавляем к релевантности документа
+ * частоту этого слова в документе, то есть число его вхождений в документ.
+ * 4) Релевантность документа — это сумма частот всех уникальных слов запроса
+ * в этом документе. Из найденных документов выбираем 5 лучших.
+ *
+ * Почему алгоритм корректен:
+ * - В индексе для каждого слова хранится точное число его вхождений в документ.
+ * - По условию релевантность — это сумма частот всех уникальных слов запроса.
+ * Именно такую сумму мы и считаем.
+ * - Повторы слов в запросе не влияют на ответ, потому что мы оставляем только
+ * уникальные слова запроса.
+ * - После подсчёта релевантностей выбираем 5 лучших документов по правилу из условия,
+ * значит ответ получается правильным.
+ *
+ * Временная сложность:
+ *
+ * Обозначения:
+ * - D — число документов;
+ * - Wd — максимальное число слов в одном документе;
+ * - Q — число запросов;
+ * - Wq — максимальное число слов в одном запросе.
+ *
+ * - Построение индекса: O(D * Wd).
+ * - Обработка одного запроса: O(Wq * D) в худшем случае,
+ * если каждое уникальное слово запроса встречается во всех документах.
+ * - Обработка всех запросов: O(D * Wd + Q * Wq * D).
+ *
+ * Пространственная сложность:
+ *
+ * - Индекс: O(P), где P — число пар (слово, документ),
+ * для которых слово хотя бы один раз встречается в документе.
+ * В худшем случае P = O(D * Wd).
+ * - Дополнительная память на один запрос: O(Wq + D).
+ */
+
+ private static HashMap> buildIndex(String[] docs) {
+ HashMap> index = new HashMap<>();
+
+ for (int i = 0; i < docs.length; i++) {
+ HashMap freq = new HashMap<>();
+ StringTokenizer st = new StringTokenizer(docs[i]);
+
+ while (st.hasMoreTokens()) {
+ String word = st.nextToken();
+ freq.put(word, freq.getOrDefault(word, 0) + 1);
+ }
+
+ int docId = i + 1;
+ for (Map.Entry entry : freq.entrySet()) {
+ index.computeIfAbsent(entry.getKey(), k -> new ArrayList<>())
+ .add(new int[]{docId, entry.getValue()});
+ }
+ }
+
+ return index;
+ }
+
+ private static String processQuery(String query, HashMap> index) {
+ HashSet uniqueWords = new HashSet<>();
+ StringTokenizer st = new StringTokenizer(query);
+
+ while (st.hasMoreTokens()) {
+ uniqueWords.add(st.nextToken());
+ }
+
+ HashMap relevance = new HashMap<>();
+
+ for (String word : uniqueWords) {
+ ArrayList docs = index.get(word);
+ if (docs == null) {
+ continue;
+ }
+
+ for (int[] pair : docs) {
+ int docId = pair[0];
+ int count = pair[1];
+ relevance.put(docId, relevance.getOrDefault(docId, 0) + count);
+ }
+ }
+
+ ArrayList best = new ArrayList<>();
+
+ for (Map.Entry entry : relevance.entrySet()) {
+ int docId = entry.getKey();
+ int score = entry.getValue();
+
+ int pos = 0;
+ while (pos < best.size() && !isBetter(docId, score, best.get(pos)[0], best.get(pos)[1])) {
+ pos++;
+ }
+
+ if (pos < 5) {
+ best.add(pos, new int[]{docId, score});
+ if (best.size() > 5) {
+ best.remove(5);
+ }
+ }
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < best.size(); i++) {
+ if (i > 0) {
+ sb.append(' ');
+ }
+ sb.append(best.get(i)[0]);
+ }
+
+ return sb.toString();
+ }
+
+ private static boolean isBetter(int docId1, int score1, int docId2, int score2) {
+ if (score1 != score2) {
+ return score1 > score2;
+ }
+ return docId1 < docId2;
+ }
+
+ private static void solve() throws Exception {
+ FastReader reader = new FastReader(System.in);
+
+ int n = reader.nextInt();
+ String[] docs = new String[n];
+ for (int i = 0; i < n; i++) {
+ docs[i] = reader.nextLine();
+ }
+
+ HashMap> index = buildIndex(docs);
+
+ int m = reader.nextInt();
+ StringBuilder out = new StringBuilder();
+
+ for (int i = 0; i < m; i++) {
+ String query = reader.nextLine();
+ out.append(processQuery(query, index)).append('\n');
+ }
+
+ System.out.print(out);
+ }
+
+ private static void test() {
+ String[] docs1 = {
+ "i love coffee",
+ "coffee with milk and sugar",
+ "free tea for everyone"
+ };
+
+ HashMap> index1 = buildIndex(docs1);
+ assertEquals("1 2", processQuery("i like black coffee without milk", index1));
+ assertEquals("3", processQuery("everyone loves new year", index1));
+ assertEquals("2 1", processQuery("Mary likes black coffee without milk", index1));
+
+ String[] docs2 = {
+ "buy flat in Moscow",
+ "rent flat in Moscow",
+ "sell flat in Moscow",
+ "want flat in Moscow like crazy",
+ "clean flat in Moscow on weekends",
+ "renovate flat in Moscow"
+ };
+
+ HashMap> index2 = buildIndex(docs2);
+ assertEquals("4 5 1 2 3", processQuery("flat in Moscow for crazy weekends", index2));
+
+ String[] docs3 = {
+ "i like dfs and bfs",
+ "i like dfs dfs",
+ "i like bfs with bfs and bfs"
+ };
+
+ HashMap> index3 = buildIndex(docs3);
+ assertEquals("3 1 2", processQuery("dfs dfs dfs dfs bfs", index3));
+ System.out.println("Test OK");
+ }
+
+ private static void assertEquals(String expected, String actual) {
+ if (!expected.equals(actual)) {
+ throw new AssertionError("expected = [" + expected + "], actual = [" + actual + "]");
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ solve();
+ }
+ }
+ private static class FastReader {
+ private final InputStream in;
+ private final byte[] buffer = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastReader(InputStream in) {
+ this.in = new BufferedInputStream(in);
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buffer);
+ ptr = 0;
+ if (len <= 0) {
+ return -1;
+ }
+ }
+ return buffer[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException();
+ }
+ } while (c <= ' ');
+
+ int value = 0;
+ while (c > ' ') {
+ value = value * 10 + (c - '0');
+ c = read();
+ }
+ return value;
+ }
+
+ String nextLine() throws IOException {
+ int c = read();
+
+ while (c == '\n' || c == '\r') {
+ c = read();
+ }
+
+ StringBuilder sb = new StringBuilder();
+ while (c != -1 && c != '\n' && c != '\r') {
+ sb.append((char) c);
+ c = read();
+ }
+
+ return sb.toString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/algorithms/sprint4/Map.java b/src/main/java/algorithms/sprint4/Map.java
new file mode 100644
index 0000000..f960990
--- /dev/null
+++ b/src/main/java/algorithms/sprint4/Map.java
@@ -0,0 +1,206 @@
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.OptionalInt;
+
+// https://contest.yandex.ru/contest/24414/run-report/160371601/
+
+public class Map {
+
+ /*
+ * Принцип работы алгоритма:
+ * Делаем свою хеш-таблицу методом цепочек.
+ * Есть массив бакетов. В каждом бакете лежит связный список элементов.
+ *
+ * Для ключа key:
+ * - считаем номер бакета по формуле key % SIZE;
+ * если остаток отрицательный, прибавляем SIZE,
+ * чтобы индекс был в диапазоне от 0 до SIZE - 1;
+ * - идём по списку этого бакета;
+ * - put: если ключ нашли, обновляем значение, иначе
+ * добавляем новый узел в начало списка бакета:
+ * buckets[i] = new Node(key, value, buckets[i]);
+ * - get: если нашли, выводим значение, иначе None;
+ * - delete: если нашли, удаляем узел из списка:
+ * если удаляется голова, делаем buckets[i] = cur.next,
+ * иначе делаем prev.next = cur.next.
+ *
+ * Почему алгоритм корректен:
+ * - Каждый ключ всегда попадает в один и тот же бакет,
+ * потому что функция index(key) зависит только от key и SIZE.
+ * - Все элементы этого бакета хранятся в одном списке.
+ * - Поэтому, если пройти весь список, мы точно:
+ * - найдём ключ, если он есть;
+ * - поймём, что его нет, если не нашли.
+ *
+ * Временная сложность:
+ * - одна операция put, get или delete в среднем работает за O(1),
+ * при достаточно равномерном распределении ключей по бакетам;
+ * - в худшем случае одна операция работает за O(n),
+ * если все ключи попали в один бакет;
+ * - так как выполняется n команд, суммарная сложность программы
+ * в среднем O(n), в худшем случае O(n^2).
+ *
+ * Пространственная сложность:
+ * - в худшем случае таблица хранит до n элементов,
+ * поэтому пространственная сложность O(n);
+ * - массив бакетов имеет фиксированный размер SIZE,
+ * то есть даёт только константную добавку.
+ */
+
+ private static final int SIZE = 100_003;
+
+ static class Node {
+ int key;
+ int value;
+ Node next;
+
+ Node(int key, int value, Node next) {
+ this.key = key;
+ this.value = value;
+ this.next = next;
+ }
+ }
+
+ static class HashTable {
+ Node[] buckets = new Node[SIZE];
+
+ int index(int key) {
+ int x = key % SIZE;
+ if (x < 0) {
+ x += SIZE;
+ }
+ return x;
+ }
+
+ void put(int key, int value) {
+ int i = index(key);
+ Node cur = buckets[i];
+
+ while (cur != null) {
+ if (cur.key == key) {
+ cur.value = value;
+ return;
+ }
+ cur = cur.next;
+ }
+
+ buckets[i] = new Node(key, value, buckets[i]);
+ }
+
+ OptionalInt get(int key) {
+ int i = index(key);
+ Node cur = buckets[i];
+
+ while (cur != null) {
+ if (cur.key == key) {
+ return OptionalInt.of(cur.value);
+ }
+ cur = cur.next;
+ }
+
+ return OptionalInt.empty();
+ }
+
+ OptionalInt delete(int key) {
+ int i = index(key);
+ Node cur = buckets[i];
+ Node prev = null;
+
+ while (cur != null) {
+ if (cur.key == key) {
+ if (prev == null) {
+ buckets[i] = cur.next;
+ } else {
+ prev.next = cur.next;
+ }
+ return OptionalInt.of(cur.value);
+ }
+ prev = cur;
+ cur = cur.next;
+ }
+
+ return OptionalInt.empty();
+ }
+ }
+
+ static class Reader {
+ private final InputStream in = new BufferedInputStream(System.in);
+ private final byte[] buffer = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ int read() throws IOException {
+ if (ptr == len) {
+ len = in.read(buffer);
+ ptr = 0;
+ if (len == -1) {
+ return -1;
+ }
+ }
+ return buffer[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c = read();
+ while (c <= ' ') {
+ c = read();
+ }
+
+ int sign = 1;
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int num = 0;
+ while (c > ' ') {
+ num = num * 10 + (c - '0');
+ c = read();
+ }
+
+ return num * sign;
+ }
+
+ char nextCommand() throws IOException {
+ int c = read();
+ while (c <= ' ') {
+ c = read();
+ }
+
+ char first = (char) c;
+
+ while (c > ' ') {
+ c = read();
+ }
+
+ return first;
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Reader reader = new Reader();
+ HashTable table = new HashTable();
+ StringBuilder out = new StringBuilder();
+
+ int n = reader.nextInt();
+
+ for (int i = 0; i < n; i++) {
+ char command = reader.nextCommand();
+ int key = reader.nextInt();
+
+ if (command == 'p') {
+ int value = reader.nextInt();
+ table.put(key, value);
+ } else if (command == 'g') {
+ table.get(key).ifPresentOrElse(out::append, () -> out.append("None"));
+ out.append(System.lineSeparator());
+ } else {
+ table.delete(key).ifPresentOrElse(out::append, () -> out.append("None"));
+ out.append(System.lineSeparator());
+ }
+ }
+
+ System.out.print(out);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/algorithms/sprint5/PyramidSort.java b/src/main/java/algorithms/sprint5/PyramidSort.java
new file mode 100644
index 0000000..3d9a159
--- /dev/null
+++ b/src/main/java/algorithms/sprint5/PyramidSort.java
@@ -0,0 +1,293 @@
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+//https://contest.yandex.ru/contest/24810/run-report/160623687/
+
+public class PyramidSort {
+
+ /*
+ * Принцип работы алгоритма:
+ * 1) Считываем всех участников в массив.
+ * 2) Сортируем массив пирамидальной сортировкой.
+ * 3) Для этого сначала строим max-heap, где наверху находится не лучший,
+ * а худший по условию участник. Это удобно для heapsort: на каждой
+ * итерации мы ставим корень кучи в конец ещё неотсортированной части.
+ * 4) Участник a считается хуже участника b, если:
+ * - a решил меньше задач;
+ * - при равенстве решённых задач у a штраф больше;
+ * - при равенстве и задач, и штрафа логин a лексикографически больше.
+ * 5) После каждого обмена восстанавливаем свойство кучи просеиванием вниз.
+ *
+ * Почему алгоритм корректен:
+ * 1) Компаратор точно совпадает с порядком из условия, только в обратном
+ * направлении: "больше" в куче означает "хуже в итоговой таблице".
+ * 2) Поэтому в корне max-heap всегда лежит худший участник среди всех
+ * элементов текущей кучи.
+ * 3) На каждом шаге heapsort мы меняем корень с последним элементом
+ * неотсортированной части. Значит, этот худший участник встаёт ровно
+ * на своё окончательное место.
+ * 4) После просеивания вниз куча снова корректна.
+ * 5) Повторяя этот процесс, получаем массив, отсортированный от лучшего
+ * участника к худшему.
+ *
+ * Временная сложность:
+ * - построение кучи: O(n);
+ * - n операций извлечения максимума: O(n log n);
+ * - итоговая сложность: O(n log n), где n количество участников;.
+ *
+ * Пространственная сложность:
+ * - O(n) на хранение массива участников, где n количество участников;
+ * - дополнительная память алгоритма сортировки: O(1).
+ */
+
+ static final class Participant {
+ String login;
+ int solved;
+ int penalty;
+
+ Participant(String login, int solved, int penalty) {
+ this.login = login;
+ this.solved = solved;
+ this.penalty = penalty;
+ }
+ }
+
+ static void solve(Participant[] a) {
+ heapSort(a);
+ }
+
+ static int compare(Participant a, Participant b) {
+ if (a.solved != b.solved) {
+ return Integer.compare(b.solved, a.solved);
+ }
+ if (a.penalty != b.penalty) {
+ return Integer.compare(a.penalty, b.penalty);
+ }
+ return a.login.compareTo(b.login);
+ }
+
+ static void heapSort(Participant[] a) {
+ int n = a.length;
+
+ for (int i = n / 2 - 1; i >= 0; i--) {
+ siftDown(a, i, n);
+ }
+
+ for (int end = n - 1; end > 0; end--) {
+ swap(a, 0, end);
+ siftDown(a, 0, end);
+ }
+ }
+
+ static void siftDown(Participant[] a, int i, int size) {
+ while (true) {
+ int left = i * 2 + 1;
+ if (left >= size) {
+ return;
+ }
+
+ int right = left + 1;
+ int worst = left;
+
+ if (right < size && compare(a[right], a[left]) > 0) {
+ worst = right;
+ }
+
+ if (compare(a[worst], a[i]) <= 0) {
+ return;
+ }
+
+ swap(a, i, worst);
+ i = worst;
+ }
+ }
+
+ static void swap(Participant[] a, int i, int j) {
+ Participant tmp = a[i];
+ a[i] = a[j];
+ a[j] = tmp;
+ }
+
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+ if (len <= 0) {
+ return -1;
+ }
+ }
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ int sign = 1;
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+ return val * sign;
+ }
+
+ String next() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ byte[] tmp = new byte[32];
+ int n = 0;
+
+ while (c > ' ') {
+ if (n == tmp.length) {
+ byte[] next = new byte[tmp.length * 2];
+ System.arraycopy(tmp, 0, next, 0, tmp.length);
+ tmp = next;
+ }
+ tmp[n++] = (byte) c;
+ c = read();
+ if (c == -1) {
+ break;
+ }
+ }
+
+ return new String(tmp, 0, n);
+ }
+ }
+
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (ptr == buf.length) {
+ flush();
+ }
+ buf[ptr++] = (byte) b;
+ }
+
+ void writeString(String s) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ writeByte(s.charAt(i));
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, ptr);
+ ptr = 0;
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ int n = in.nextInt();
+ Participant[] a = new Participant[n];
+
+ for (int i = 0; i < n; i++) {
+ String login = in.next();
+ int solved = in.nextInt();
+ int penalty = in.nextInt();
+ a[i] = new Participant(login, solved, penalty);
+ }
+
+ solve(a);
+
+ for (Participant p : a) {
+ out.writeString(p.login);
+ out.writeByte('\n');
+ }
+ out.flush();
+ }
+
+ private static void test() {
+ Participant[] a1 = new Participant[] {
+ new Participant("alla", 4, 100),
+ new Participant("gena", 6, 1000),
+ new Participant("gosha", 2, 90),
+ new Participant("rita", 2, 90),
+ new Participant("timofey", 4, 80)
+ };
+ solve(a1);
+ assertEq("gena", a1[0].login);
+ assertEq("timofey", a1[1].login);
+ assertEq("alla", a1[2].login);
+ assertEq("gosha", a1[3].login);
+ assertEq("rita", a1[4].login);
+
+ Participant[] a2 = new Participant[] {
+ new Participant("alla", 0, 0),
+ new Participant("gena", 0, 0),
+ new Participant("gosha", 0, 0),
+ new Participant("rita", 0, 0),
+ new Participant("timofey", 0, 0)
+ };
+ solve(a2);
+ assertEq("alla", a2[0].login);
+ assertEq("gena", a2[1].login);
+ assertEq("gosha", a2[2].login);
+ assertEq("rita", a2[3].login);
+ assertEq("timofey", a2[4].login);
+
+ Participant[] a3 = new Participant[] {
+ new Participant("b", 1, 10),
+ new Participant("a", 1, 10),
+ new Participant("c", 2, 100)
+ };
+ solve(a3);
+ assertEq("c", a3[0].login);
+ assertEq("a", a3[1].login);
+ assertEq("b", a3[2].login);
+
+ System.out.println("Test OK");
+ }
+
+ static void assertEq(String expected, String actual) {
+ if (!expected.equals(actual)) {
+ throw new AssertionError("Expected=" + expected + ", actual=" + actual);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}
+
diff --git a/src/main/java/algorithms/sprint5/Solution.java b/src/main/java/algorithms/sprint5/Solution.java
new file mode 100644
index 0000000..e1a1bfb
--- /dev/null
+++ b/src/main/java/algorithms/sprint5/Solution.java
@@ -0,0 +1,124 @@
+// https://contest.yandex.ru/contest/24810/run-report/160624089/
+
+public class Solution {
+
+ /*
+ * Принцип работы алгоритма:
+ * Используем свойство бинарного дерева поиска.
+ * Если удаляемый ключ меньше значения в текущей вершине,
+ * продолжаем поиск в левом поддереве.
+ * Если больше — в правом.
+ * Когда вершина с нужным ключом найдена, возможны три случая:
+ *
+ * 1) У вершины нет левого ребёнка.
+ * Тогда на её место должно встать правое поддерево.
+ *
+ * 2) У вершины нет правого ребёнка.
+ * Тогда на её место должно встать левое поддерево.
+ *
+ * 3) У вершины есть оба ребёнка.
+ * Тогда берём максимальный элемент из левого поддерева
+ * (это предшественник текущей вершины в порядке BST),
+ * записываем его значение в текущую вершину,
+ * а затем удаляем этот элемент из левого поддерева.
+ *
+ * Почему алгоритм корректен:
+ * 1) По свойству BST искомый ключ может находиться только
+ * в одном из двух поддеревьев, поэтому рекурсивный спуск
+ * идёт ровно по нужному пути.
+ *
+ * 2) Если у удаляемой вершины не более одного ребёнка,
+ * замена вершины её единственным поддеревом
+ * сохраняет порядок ключей в дереве.
+ *
+ * 3) Если у вершины два ребёнка, максимум левого поддерева:
+ * - меньше значения любой вершины из правого поддерева;
+ * - не меньше всех остальных значений из левого поддерева.
+ * Поэтому после замены текущего ключа на этот максимум
+ * свойство BST сохраняется.
+ *
+ * 4) После этого мы удаляем из левого поддерева вершину,
+ * значение которой перенесли вверх. Так как ключи уникальны,
+ * удаляется ровно одна нужная вершина.
+ *
+ * Временная сложность: O(h), где h — высота дерева,
+ * что в худшем случае даёт O(n), где n — число узлов в дереве.
+ *
+ * Дополнительная пространственная сложность: O(h) из-за стека рекурсии,
+ * что в худшем случае даёт O(n).
+ */
+
+ public static Node remove(Node root, int key) {
+ if (root == null) {
+ return null;
+ }
+
+ if (key < root.getValue()) {
+ root.setLeft(remove(root.getLeft(), key));
+ return root;
+ }
+
+ if (key > root.getValue()) {
+ root.setRight(remove(root.getRight(), key));
+ return root;
+ }
+
+ if (root.getLeft() == null) {
+ return root.getRight();
+ }
+
+ if (root.getRight() == null) {
+ return root.getLeft();
+ }
+
+ Node predecessor = findMax(root.getLeft());
+ root.setValue(predecessor.getValue());
+ root.setLeft(remove(root.getLeft(), predecessor.getValue()));
+ return root;
+ }
+
+ private static Node findMax(Node node) {
+ while (node.getRight() != null) {
+ node = node.getRight();
+ }
+ return node;
+ }
+}
+
+//
+class Node {
+ private int value;
+ private Node left;
+ private Node right;
+
+ Node(Node left, Node right, int value) {
+ this.left = left;
+ this.right = right;
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public Node getRight() {
+ return right;
+ }
+
+ public void setRight(Node right) {
+ this.right = right;
+ }
+
+ public Node getLeft() {
+ return left;
+ }
+
+ public void setLeft(Node left) {
+ this.left = left;
+ }
+
+ public void setValue(int value) {
+ this.value = value;
+ }
+}
+//
\ No newline at end of file
diff --git a/src/main/java/algorithms/sprint6/DorogayaSet.java b/src/main/java/algorithms/sprint6/DorogayaSet.java
new file mode 100644
index 0000000..51e3fee
--- /dev/null
+++ b/src/main/java/algorithms/sprint6/DorogayaSet.java
@@ -0,0 +1,304 @@
+/*
+ * Принцип работы алгоритма:
+ * Нужно найти максимальное остовное дерево, поэтому используем алгоритм Краскала
+ * в обратном порядке: сортируем все рёбра по убыванию веса и последовательно
+ * добавляем ребро в ответ, если оно соединяет две разные компоненты связности.
+ * Для проверки компонент используем DSU (Disjoint Set Union, система
+ * непересекающихся множеств). DSU хранит разбиение вершин на компоненты:
+ * parent[v] ведёт к представителю компоненты вершины v, а size[root] хранит
+ * размер компоненты с корнем root. Операция find(v) возвращает корень
+ * компоненты и сжимает путь до него, чтобы следующие запросы были быстрее.
+ * Операция union(a, b) объединяет компоненты вершин a и b, если они разные.
+ * Если find(a) == find(b), значит вершины уже соединены выбранными рёбрами,
+ * и добавление ребра a-b создало бы цикл, поэтому такое ребро пропускаем.
+ *
+ * Петли не влияют на ответ: ребро из вершины в саму себя не соединяет разные
+ * компоненты, поэтому DSU его не добавит. Кратные рёбра обрабатываются естественно:
+ * из них раньше будут рассмотрены более тяжёлые.
+ *
+ * Почему алгоритм корректен:
+ * В алгоритме Краскала на каждом шаге выбирается самое тяжёлое ребро, которое
+ * не создаёт цикл. По свойству разреза для максимального остовного дерева такое
+ * ребро можно безопасно добавить в некоторый оптимальный остов. Повторяя этот
+ * шаг, получаем максимальное остовное дерево. Если после обработки всех рёбер
+ * выбрано меньше n - 1 ребра, значит граф несвязный и остовного дерева нет.
+ *
+ * Временная сложность: O(m log m), где m — число рёбер.
+ * Основное время занимает сортировка рёбер. Операции DSU работают почти за O(1),
+ * точнее O(alpha(n)), где n — число вершин, а alpha(n) — обратная функция Аккермана.
+ * Она растёт настолько медленно, что для практических размеров входа считается константой.
+ * Пространственная сложность: O(n + m), где n — число вершин, m — число рёбер.
+ */
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+// https://contest.yandex.ru/contest/25070/run-report/162675299/
+
+public class DorogayaSet {
+
+ static final String FAIL = "Oops! I did it again";
+
+ static long solve(int n, Edge[] edges) {
+ Arrays.sort(edges, (a, b) -> Integer.compare(b.weight, a.weight));
+
+ DSU dsu = new DSU(n);
+ long totalWeight = 0;
+ int usedEdges = 0;
+
+ for (Edge edge : edges) {
+ if (dsu.union(edge.from, edge.to)) {
+ totalWeight += edge.weight;
+ usedEdges++;
+
+ if (usedEdges == n - 1) {
+ break;
+ }
+ }
+ }
+
+ if (usedEdges != n - 1) {
+ return -1;
+ }
+
+ return totalWeight;
+ }
+
+ static final class Edge {
+ final int from;
+ final int to;
+ final int weight;
+
+ Edge(int from, int to, int weight) {
+ this.from = from;
+ this.to = to;
+ this.weight = weight;
+ }
+ }
+
+ static final class DSU {
+ private final int[] parent;
+ private final int[] size;
+
+ DSU(int n) {
+ parent = new int[n + 1];
+ size = new int[n + 1];
+
+ for (int i = 1; i <= n; i++) {
+ parent[i] = i;
+ size[i] = 1;
+ }
+ }
+
+ int find(int v) {
+ if (parent[v] != v) {
+ parent[v] = find(parent[v]);
+ }
+ return parent[v];
+ }
+
+ boolean union(int a, int b) {
+ int rootA = find(a);
+ int rootB = find(b);
+
+ if (rootA == rootB) {
+ return false;
+ }
+
+ if (size[rootA] < size[rootB]) {
+ int tmp = rootA;
+ rootA = rootB;
+ rootB = tmp;
+ }
+
+ parent[rootB] = rootA;
+ size[rootA] += size[rootB];
+ return true;
+ }
+ }
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+
+ if (len <= 0) {
+ return -1;
+ }
+ }
+
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+
+ do {
+ c = read();
+
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ int sign = 1;
+
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+
+ return val * sign;
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+ private final byte[] tmp = new byte[20];
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) {
+ flush();
+ }
+
+ buf[p++] = (byte) b;
+ }
+
+ void writeString(String s) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ writeByte(s.charAt(i));
+ }
+ }
+
+ void writeLong(long x) throws IOException {
+ if (x == 0) {
+ writeByte('0');
+ return;
+ }
+
+ if (x < 0) {
+ writeByte('-');
+ x = -x;
+ }
+
+ int k = 0;
+
+ while (x > 0) {
+ tmp[k++] = (byte) ('0' + (x % 10));
+ x /= 10;
+ }
+
+ for (int i = k - 1; i >= 0; i--) {
+ writeByte(tmp[i]);
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ int n = in.nextInt();
+ int m = in.nextInt();
+
+ Edge[] edges = new Edge[m];
+
+ for (int i = 0; i < m; i++) {
+ int from = in.nextInt();
+ int to = in.nextInt();
+ int weight = in.nextInt();
+
+ edges[i] = new Edge(from, to, weight);
+ }
+
+ long answer = solve(n, edges);
+
+ if (answer == -1) {
+ out.writeString(FAIL);
+ } else {
+ out.writeLong(answer);
+ }
+
+ out.writeByte('\n');
+ out.flush();
+ }
+
+ private static void test() {
+ assertEq(19, solve(4, new Edge[]{
+ new Edge(1, 2, 5),
+ new Edge(1, 3, 6),
+ new Edge(2, 4, 8),
+ new Edge(3, 4, 3)
+ }));
+
+ assertEq(3, solve(3, new Edge[]{
+ new Edge(1, 2, 1),
+ new Edge(1, 2, 2),
+ new Edge(2, 3, 1)
+ }));
+
+ assertEq(-1, solve(2, new Edge[]{}));
+
+ assertEq(0, solve(1, new Edge[]{}));
+
+ assertEq(10, solve(2, new Edge[]{
+ new Edge(1, 1, 100),
+ new Edge(1, 2, 10),
+ new Edge(2, 2, 100)
+ }));
+
+ assertEq(0, solve(3, new Edge[]{
+ new Edge(1, 2, 0),
+ new Edge(2, 3, 0)
+ }));
+
+ System.out.println("Test OK");
+ }
+
+ static void assertEq(long exp, long act) {
+ if (exp != act) {
+ throw new AssertionError("Expected=" + exp + ", actual=" + act);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}
diff --git a/src/main/java/algorithms/sprint6/WaterWorld.java b/src/main/java/algorithms/sprint6/WaterWorld.java
new file mode 100644
index 0000000..49d083b
--- /dev/null
+++ b/src/main/java/algorithms/sprint6/WaterWorld.java
@@ -0,0 +1,292 @@
+/*
+ * Принцип работы алгоритма:
+ * Поле рассматривается как неориентированный граф: каждая клетка земли '#' — вершина,
+ * а рёбра есть между соседними по стороне клетками земли. Идём по всем клеткам поля.
+ * Если встречаем ещё не обработанную землю, значит найден новый остров. Запускаем из неё
+ * итеративный BFS, помечаем все клетки этого острова как воду '.', одновременно считаем
+ * размер острова и обновляем максимум.
+ *
+ * Почему алгоритм корректен:
+ * Остров — это компонента связности клеток земли по четырём направлениям. BFS из любой
+ * клетки такой компоненты посещает ровно все клетки этой компоненты: к каждой достижимой
+ * по сторонам клетке он перейдёт, а за пределы компоненты не выйдет, потому что переходит
+ * только в клетки '#'. После посещения клетки помечаются как '.', поэтому один и тот же
+ * остров не будет посчитан повторно. Значит, каждое новое начало BFS соответствует ровно
+ * одному острову, а посчитанный внутри BFS размер равен количеству клеток этого острова.
+ * Перебор всех клеток гарантирует, что будут найдены все острова.
+ *
+ * Временная сложность: O(n * m), где n — количество строк, m — количество столбцов.
+ * Каждая клетка проверяется и обрабатывается не более одного раза.
+ * Пространственная сложность: O(n * m), где n — количество строк, m — количество столбцов.
+ * Храним поле и очередь для BFS.
+ */
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+// https://contest.yandex.ru/contest/25070/run-report/162675302/
+
+public class WaterWorld {
+
+ static int[] solve(byte[] map, int n, int m) {
+ int total = n * m;
+ int[] queue = new int[total];
+
+ int islandCount = 0;
+ int maxIslandSize = 0;
+
+ for (int start = 0; start < total; start++) {
+ if (map[start] != '#') {
+ continue;
+ }
+
+ islandCount++;
+ int currentSize = 0;
+ int head = 0;
+ int tail = 0;
+
+ queue[tail++] = start;
+ map[start] = '.';
+
+ while (head < tail) {
+ int v = queue[head++];
+ currentSize++;
+
+ int col = v % m;
+
+ int up = v - m;
+ if (v >= m && map[up] == '#') {
+ map[up] = '.';
+ queue[tail++] = up;
+ }
+
+ int down = v + m;
+ if (down < total && map[down] == '#') {
+ map[down] = '.';
+ queue[tail++] = down;
+ }
+
+ int left = v - 1;
+ if (col > 0 && map[left] == '#') {
+ map[left] = '.';
+ queue[tail++] = left;
+ }
+
+ int right = v + 1;
+ if (col + 1 < m && map[right] == '#') {
+ map[right] = '.';
+ queue[tail++] = right;
+ }
+ }
+
+ if (currentSize > maxIslandSize) {
+ maxIslandSize = currentSize;
+ }
+ }
+
+ return new int[]{islandCount, maxIslandSize};
+ }
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+ if (len <= 0) {
+ return -1;
+ }
+ }
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ int sign = 1;
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+ return val * sign;
+ }
+
+ byte[] nextBytes(int length) throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ byte[] result = new byte[length];
+ for (int i = 0; i < length; i++) {
+ if (c <= ' ') {
+ throw new EOFException("Unexpected end of token");
+ }
+ result[i] = (byte) c;
+ if (i + 1 < length) {
+ c = read();
+ }
+ }
+ return result;
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+ private final byte[] tmp = new byte[12];
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) {
+ flush();
+ }
+ buf[p++] = (byte) b;
+ }
+
+ void writeInt(int x) throws IOException {
+ if (x == 0) {
+ writeByte('0');
+ return;
+ }
+ if (x < 0) {
+ writeByte('-');
+ x = -x;
+ }
+
+ int k = 0;
+ while (x > 0) {
+ tmp[k++] = (byte) ('0' + (x % 10));
+ x /= 10;
+ }
+ for (int i = k - 1; i >= 0; i--) {
+ writeByte(tmp[i]);
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ int n = in.nextInt();
+ int m = in.nextInt();
+ byte[] map = new byte[n * m];
+
+ for (int row = 0; row < n; row++) {
+ byte[] line = in.nextBytes(m);
+ System.arraycopy(line, 0, map, row * m, m);
+ }
+
+ int[] answer = solve(map, n, m);
+ out.writeInt(answer[0]);
+ out.writeByte(' ');
+ out.writeInt(answer[1]);
+ out.writeByte('\n');
+ out.flush();
+ }
+
+ private static void test() {
+ assertAnswer(5, 1, solve(map(
+ "#.#",
+ ".#.",
+ "#.#"
+ ), 3, 3));
+
+ assertAnswer(2, 6, solve(map(
+ "#####",
+ ".#...",
+ "..#..",
+ "#####"
+ ), 4, 5));
+
+ assertAnswer(0, 0, solve(map(
+ "...",
+ "..."
+ ), 2, 3));
+
+ assertAnswer(1, 6, solve(map(
+ "###",
+ "###"
+ ), 2, 3));
+
+ assertAnswer(4, 1, solve(map(
+ "#.#",
+ "...",
+ "#.#"
+ ), 3, 3));
+
+ assertAnswer(2, 3, solve(map(
+ "##.",
+ "#..",
+ "..#"
+ ), 3, 3));
+
+ System.out.println("Test OK");
+ }
+
+ private static byte[] map(String... rows) {
+ int n = rows.length;
+ int m = rows[0].length();
+ byte[] result = new byte[n * m];
+ for (int row = 0; row < n; row++) {
+ for (int col = 0; col < m; col++) {
+ result[row * m + col] = (byte) rows[row].charAt(col);
+ }
+ }
+ return result;
+ }
+
+ private static void assertAnswer(int expectedCount, int expectedMaxSize, int[] actual) {
+ if (actual[0] != expectedCount || actual[1] != expectedMaxSize) {
+ throw new AssertionError(
+ "Expected=" + expectedCount + " " + expectedMaxSize
+ + ", actual=" + actual[0] + " " + actual[1]
+ );
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}
diff --git a/src/main/java/algorithms/sprint7/EqualSums.java b/src/main/java/algorithms/sprint7/EqualSums.java
new file mode 100644
index 0000000..e8bb418
--- /dev/null
+++ b/src/main/java/algorithms/sprint7/EqualSums.java
@@ -0,0 +1,206 @@
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/*
+Принцип работы алгоритма:
+Нужно понять, можно ли выбрать часть партий так, чтобы сумма очков в них была равна половине общей суммы.
+Если общая сумма нечётная, две равные части получить нельзя.
+Иначе решаем задачу о достижимой сумме: храним битовую динамику reachable, где бит s равен 1,
+если некоторым набором уже рассмотренных партий можно набрать сумму s. Изначально достижима сумма 0.
+Для очередного значения x обновляем reachable операцией reachable |= reachable << x.
+После обработки всех значений проверяем достижимость суммы total / 2.
+
+Доказательство корректности:
+После обработки первых i партий инвариант такой: бит s в reachable установлен тогда и только тогда,
+когда из этих i партий можно выбрать подмножество с суммой s.
+База: до обработки партий достижима только сумма 0, это пустое подмножество.
+Переход: для партии с очками x каждая старая достижимая сумма s остаётся достижимой, если не брать эту партию,
+а сумма s + x становится достижимой, если взять её. Именно это делает reachable |= reachable << x.
+По индукции после всех партий reachable содержит ровно все суммы, которые можно набрать подмножеством партий.
+Разбиение на две равные части существует тогда и только тогда, когда общая сумма чётная и достижима сумма total / 2:
+выбранное подмножество даёт первую часть, все остальные партии — вторую с такой же суммой.
+
+Сложность:
+Пусть n — количество партий, S — общая сумма очков.
+Так как n <= 300 и каждое значение <= 300, S <= 300 * 300 = 90000.
+Временная сложность — O(n * S / 64), потому что суммы хранятся битами в long-блоках.
+Пространственная сложность — O(S / 64).
+*/
+public class EqualSums {
+
+ static boolean solve(int[] points) {
+ int total = 0;
+ for (int point : points) {
+ total += point;
+ }
+
+ if ((total & 1) == 1) {
+ return false;
+ }
+
+ int target = total / 2;
+ long[] reachable = new long[(target >> 6) + 1];
+ reachable[0] = 1L;
+
+ for (int point : points) {
+ if (point <= 0 || point > target) {
+ continue;
+ }
+
+ addPoint(reachable, point);
+
+ if (isReachable(reachable, target)) {
+ return true;
+ }
+ }
+
+ return isReachable(reachable, target);
+ }
+
+ private static void addPoint(long[] reachable, int point) {
+ int wordShift = point >> 6;
+ int bitShift = point & 63;
+
+ for (int word = reachable.length - 1; word >= wordShift; word--) {
+ long shifted = reachable[word - wordShift] << bitShift;
+
+ if (bitShift != 0 && word - wordShift - 1 >= 0) {
+ shifted |= reachable[word - wordShift - 1] >>> (64 - bitShift);
+ }
+
+ reachable[word] |= shifted;
+ }
+ }
+
+ private static boolean isReachable(long[] reachable, int sum) {
+ return (reachable[sum >> 6] & (1L << (sum & 63))) != 0;
+ }
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+
+ if (len <= 0) {
+ return -1;
+ }
+ }
+
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+
+ do {
+ c = read();
+
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ int sign = 1;
+
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+
+ return val * sign;
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) {
+ flush();
+ }
+
+ buf[p++] = (byte) b;
+ }
+
+ void writeAscii(String s) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ writeByte(s.charAt(i));
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ int n = in.nextInt();
+ int[] points = new int[n];
+
+ for (int i = 0; i < n; i++) {
+ points[i] = in.nextInt();
+ }
+
+ out.writeAscii(solve(points) ? "True" : "False");
+ out.writeByte('\n');
+ out.flush();
+ }
+
+ private static void test() {
+ assertEq(true, solve(new int[]{1, 5, 7, 1}));
+ assertEq(false, solve(new int[]{2, 10, 9}));
+ assertEq(true, solve(new int[]{}));
+ assertEq(true, solve(new int[]{0, 0, 0}));
+ assertEq(false, solve(new int[]{1}));
+ assertEq(true, solve(new int[]{1, 1}));
+ assertEq(true, solve(new int[]{100, 200, 300}));
+ assertEq(false, solve(new int[]{100, 200, 301}));
+
+ System.out.println("Test OK");
+ }
+
+ static void assertEq(boolean exp, boolean act) {
+ if (exp != act) {
+ throw new AssertionError("Expected=" + exp + ", actual=" + act);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/algorithms/sprint7/LevenshteinDistance.java b/src/main/java/algorithms/sprint7/LevenshteinDistance.java
new file mode 100644
index 0000000..1e22c6e
--- /dev/null
+++ b/src/main/java/algorithms/sprint7/LevenshteinDistance.java
@@ -0,0 +1,205 @@
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/*
+Принцип работы алгоритма:
+Используем динамическое программирование по префиксам строк. Пусть dp[i][j] — минимальное
+число операций, чтобы превратить первые i символов первой строки в первые j символов второй.
+Переход берёт минимум из трёх вариантов: удалить символ, вставить символ или заменить символ.
+Если текущие символы равны, замена имеет цену 0. Полную таблицу хранить не нужно: для расчёта
+текущей строки достаточно предыдущей строки и уже посчитанных значений текущей строки.
+
+Доказательство корректности:
+База корректна: пустую строку можно превратить в префикс длины j ровно j вставками, а префикс
+длины i в пустую строку — ровно i удалениями. Для непустых префиксов последняя операция в любом
+оптимальном преобразовании — это либо удаление последнего символа первой строки, либо вставка
+последнего символа второй строки, либо замена/сохранение последнего символа. Алгоритм перебирает
+все эти возможные последние операции и выбирает минимум, значит находит оптимальное значение для
+каждой пары префиксов. По индукции по сумме длин префиксов значение для полных строк тоже
+оптимально.
+
+Временная сложность: O(n * m), где n и m — длины строк.
+Пространственная сложность: O(min(n, m)), потому что хранится только две строки динамики.
+*/
+public class LevenshteinDistance {
+
+ static int solve(String first, String second) {
+ String s = first;
+ String t = second;
+
+ if (t.length() > s.length()) {
+ String tmp = s;
+ s = t;
+ t = tmp;
+ }
+
+ int n = s.length();
+ int m = t.length();
+
+ int[] previous = new int[m + 1];
+ int[] current = new int[m + 1];
+
+ for (int j = 0; j <= m; j++) {
+ previous[j] = j;
+ }
+
+ for (int i = 1; i <= n; i++) {
+ current[0] = i;
+ char fromChar = s.charAt(i - 1);
+
+ for (int j = 1; j <= m; j++) {
+ int deletion = previous[j] + 1;
+ int insertion = current[j - 1] + 1;
+
+ int replacement = previous[j - 1];
+ if (fromChar != t.charAt(j - 1)) {
+ replacement++;
+ }
+
+ current[j] = Math.min(Math.min(deletion, insertion), replacement);
+ }
+
+ int[] tmp = previous;
+ previous = current;
+ current = tmp;
+ }
+
+ return previous[m];
+ }
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+ if (len <= 0) {
+ return -1;
+ }
+ }
+ return buf[ptr++];
+ }
+
+ String nextLine() throws IOException {
+ byte[] tmp = new byte[1024];
+ int size = 0;
+ int c = read();
+
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+
+ while (c != -1 && c != '\n') {
+ if (c != '\r') {
+ if (size == tmp.length) {
+ byte[] grown = new byte[tmp.length * 2];
+ System.arraycopy(tmp, 0, grown, 0, tmp.length);
+ tmp = grown;
+ }
+ tmp[size++] = (byte) c;
+ }
+ c = read();
+ }
+
+ return new String(tmp, 0, size);
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+ private final byte[] tmp = new byte[12];
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) {
+ flush();
+ }
+ buf[p++] = (byte) b;
+ }
+
+ void writeInt(int x) throws IOException {
+ if (x == 0) {
+ writeByte('0');
+ return;
+ }
+
+ if (x < 0) {
+ writeByte('-');
+ x = -x;
+ }
+
+ int k = 0;
+ while (x > 0) {
+ tmp[k++] = (byte) ('0' + (x % 10));
+ x /= 10;
+ }
+
+ for (int i = k - 1; i >= 0; i--) {
+ writeByte(tmp[i]);
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ String s = in.nextLine();
+ String t = in.nextLine();
+
+ out.writeInt(solve(s, t));
+ out.writeByte('\n');
+ out.flush();
+ }
+
+ private static void test() {
+ assertEq(2, solve("abacaba", "abaabc"));
+ assertEq(3, solve("innokentiy", "innnokkentia"));
+ assertEq(1, solve("r", "x"));
+
+ assertEq(0, solve("abc", "abc"));
+ assertEq(3, solve("", "abc"));
+ assertEq(3, solve("abc", ""));
+ assertEq(3, solve("kitten", "sitting"));
+ assertEq(1, solve("abc", "ab"));
+ assertEq(1, solve("ab", "abc"));
+
+ System.out.println("Test OK");
+ }
+
+ static void assertEq(int expected, int actual) {
+ if (expected != actual) {
+ throw new AssertionError("Expected=" + expected + ", actual=" + actual);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/algorithms/sprint8/Crib.java b/src/main/java/algorithms/sprint8/Crib.java
new file mode 100644
index 0000000..0e47516
--- /dev/null
+++ b/src/main/java/algorithms/sprint8/Crib.java
@@ -0,0 +1,259 @@
+/*
+Принцип работы алгоритма:
+строим бор по допустимым словам. Затем запускаем динамику по тексту:
+dp[i] означает, что префикс text[0..i) можно разбить на слова из словаря.
+Из каждой достижимой позиции i идём по бору вдоль text[i..]. Каждый раз,
+когда попадаем в терминальную вершину, помечаем позицию после найденного слова
+как достижимую.
+
+Корректность:
+База dp[0] истинна, потому что пустой префикс уже корректно разбит.
+Если dp[i] истинна и из позиции i в боре найдено слово text[i..j), то префикс
+text[0..j) разбивается корректно: сначала берём корректное разбиение text[0..i),
+затем добавляем найденное слово. Поэтому каждый переход алгоритма сохраняет
+корректность.
+Обратно, если текст имеет корректное разбиение на слова w1, w2, ..., wk, то после
+обработки позиции 0 алгоритм отметит конец w1, затем из этой позиции отметит
+конец w2 и так далее. По индукции будет отмечена позиция |text|. Значит,
+алгоритм вернёт YES тогда и только тогда, когда разбиение существует.
+
+Сложность:
+Пусть N = |text|, S — сумма длин слов, L — максимальная длина слова.
+Построение бора занимает O(S). Динамика проходит из каждой достижимой позиции
+не глубже L символов, поэтому работает за O(N * L). Здесь L <= 100.
+Память: O(S * 26 + N) на бор и массив dp.
+*/
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class Crib {
+
+ static boolean solve(String text, String[] words) {
+ int totalLength = 0;
+ for (String word : words) {
+ totalLength += word.length();
+ }
+
+ Trie trie = new Trie(totalLength + 1);
+ for (String word : words) {
+ trie.add(word);
+ }
+
+ char[] chars = text.toCharArray();
+ boolean[] dp = new boolean[chars.length + 1];
+ dp[0] = true;
+
+ for (int start = 0; start < chars.length; start++) {
+ if (!dp[start]) {
+ continue;
+ }
+
+ int node = 0;
+ for (int pos = start; pos < chars.length; pos++) {
+ int letter = chars[pos] - 'a';
+ node = trie.next[node][letter];
+
+ if (node == 0) {
+ break;
+ }
+
+ if (trie.terminal[node]) {
+ dp[pos + 1] = true;
+ }
+ }
+
+ if (dp[chars.length]) {
+ return true;
+ }
+ }
+
+ return dp[chars.length];
+ }
+
+ static final class Trie {
+ final int[][] next;
+ final boolean[] terminal;
+ int size = 1;
+
+ Trie(int capacity) {
+ next = new int[capacity][26];
+ terminal = new boolean[capacity];
+ }
+
+ void add(String word) {
+ int node = 0;
+ for (int i = 0; i < word.length(); i++) {
+ int letter = word.charAt(i) - 'a';
+ if (next[node][letter] == 0) {
+ next[node][letter] = size;
+ size++;
+ }
+ node = next[node][letter];
+ }
+ terminal[node] = true;
+ }
+ }
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+ if (len <= 0) {
+ return -1;
+ }
+ }
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ int sign = 1;
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+ return val * sign;
+ }
+
+ String next() throws IOException {
+ int c;
+ do {
+ c = read();
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ byte[] tmp = new byte[128];
+ int n = 0;
+ while (c > ' ') {
+ if (n == tmp.length) {
+ byte[] resized = new byte[tmp.length * 2];
+ System.arraycopy(tmp, 0, resized, 0, tmp.length);
+ tmp = resized;
+ }
+ tmp[n++] = (byte) c;
+ c = read();
+ if (c == -1) {
+ break;
+ }
+ }
+ return new String(tmp, 0, n);
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) {
+ flush();
+ }
+ buf[p++] = (byte) b;
+ }
+
+ void writeString(String s) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ writeByte(s.charAt(i));
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ String text = in.next();
+ int n = in.nextInt();
+ String[] words = new String[n];
+
+ for (int i = 0; i < n; i++) {
+ words[i] = in.next();
+ }
+
+ out.writeString(solve(text, words) ? "YES" : "NO");
+ out.writeByte('\n');
+ out.flush();
+ }
+
+ private static void test() {
+ assertEq(true, solve("examiwillpasstheexam", new String[]{
+ "will", "pass", "the", "exam", "i"
+ }));
+
+ assertEq(false, solve("abacaba", new String[]{
+ "abac", "caba"
+ }));
+
+ assertEq(true, solve("abacaba", new String[]{
+ "abac", "caba", "aba"
+ }));
+
+ assertEq(true, solve("aaaaaa", new String[]{
+ "a", "aa", "aaa"
+ }));
+
+ assertEq(false, solve("aaaaab", new String[]{
+ "a", "aa", "aaa"
+ }));
+
+ assertEq(true, solve("leetcode", new String[]{
+ "leet", "code"
+ }));
+
+ System.out.println("Test OK");
+ }
+
+ static void assertEq(boolean exp, boolean act) {
+ if (exp != act) {
+ throw new AssertionError("Expected=" + exp + ", actual=" + act);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}
diff --git a/src/main/java/algorithms/sprint8/PackedPrefix.java b/src/main/java/algorithms/sprint8/PackedPrefix.java
new file mode 100644
index 0000000..843e30c
--- /dev/null
+++ b/src/main/java/algorithms/sprint8/PackedPrefix.java
@@ -0,0 +1,422 @@
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/*
+ * Принцип работы алгоритма:
+ * Сначала распаковываем первую строку: она задаёт максимальную возможную длину ответа.
+ * Для каждой следующей строки генерируем символы её распакованного вида только до текущей
+ * длины общего префикса и сразу сравниваем их с первой распакованной строкой.
+ *
+ * Распаковка сделана итеративно, без рекурсии. Сначала строится массив соответствия
+ * открывающих и закрывающих скобок. Затем стек кадров имитирует рекурсивный обход:
+ * буквы выдаются как есть, а конструкция k[A] обходит содержимое A ровно k раз.
+ * Конструкции 1[A] предварительно заменяются на A, чтобы длинные цепочки единичных
+ * повторов не заставляли многократно проходить одни и те же скобки.
+ *
+ * Почему алгоритм корректен:
+ * Общий префикс уже обработанных строк всегда является префиксом первой строки.
+ * При добавлении очередной строки новый общий префикс не может быть длиннее текущего,
+ * поэтому достаточно сравнить только первые prefixLength символов её распакованного вида.
+ * Итеративный распаковщик выдаёт символы в том же порядке, что и рекурсивное определение ЗС:
+ * конкатенация сохраняет порядок частей, а k[A] повторяет A ровно k раз.
+ * Первая позиция несовпадения даёт новую длину общего префикса. Если строка закончилась
+ * раньше, новая длина равна длине этой строки. По индукции после обработки всех строк
+ * получаем наибольший общий префикс всех распакованных строк.
+ *
+ * Временная сложность:
+ * Для каждой строки: O(m + p), где m — длина запакованной строки, p — число реально
+ * проверенных символов распакованной строки, p <= 100000.
+ * В худшем случае: O(n * L), где L <= 100000.
+ *
+ * Пространственная сложность:
+ * O(L + m), где L — длина первой распакованной строки, m — длина текущей запакованной строки.
+ */
+public class PackedPrefix {
+ private static final int MAX_UNPACKED_LENGTH = 100_000;
+
+ static String solve(String[] packedStrings) {
+ String first = decodePrefix(packedStrings[0], MAX_UNPACKED_LENGTH);
+ int prefixLength = first.length();
+
+ for (int i = 1; i < packedStrings.length; i++) {
+ if (prefixLength == 0) {
+ break;
+ }
+ prefixLength = commonPrefixWithPacked(packedStrings[i], first, prefixLength);
+ }
+
+ return first.substring(0, prefixLength);
+ }
+
+ private static String decodePrefix(String packed, int limit) {
+ if (limit == 0) {
+ return "";
+ }
+
+ packed = removeSingleRepeats(packed);
+
+ int[] matchingBracket = buildMatchingBrackets(packed);
+ int maxFrames = packed.length() + 1;
+ int[] starts = new int[maxFrames];
+ int[] positions = new int[maxFrames];
+ int[] ends = new int[maxFrames];
+ int[] repeatsLeft = new int[maxFrames];
+
+ int top = 0;
+ starts[top] = 0;
+ positions[top] = 0;
+ ends[top] = packed.length();
+ repeatsLeft[top] = 1;
+
+ StringBuilder decoded = new StringBuilder(Math.min(limit, packed.length()));
+
+ while (top >= 0 && decoded.length() < limit) {
+ if (positions[top] >= ends[top]) {
+ repeatsLeft[top]--;
+
+ if (repeatsLeft[top] > 0) {
+ positions[top] = starts[top];
+ } else {
+ top--;
+ }
+
+ continue;
+ }
+
+ char current = packed.charAt(positions[top]);
+
+ if (current >= 'a' && current <= 'z') {
+ decoded.append(current);
+ positions[top]++;
+ } else {
+ int repeat = current - '0';
+ int openBracket = positions[top] + 1;
+ int closeBracket = matchingBracket[openBracket];
+
+ positions[top] = closeBracket + 1;
+
+ top++;
+ starts[top] = openBracket + 1;
+ positions[top] = openBracket + 1;
+ ends[top] = closeBracket;
+ repeatsLeft[top] = repeat;
+ }
+ }
+
+ return decoded.toString();
+ }
+
+ private static int commonPrefixWithPacked(String packed, String base, int limit) {
+ if (limit == 0) {
+ return 0;
+ }
+
+ packed = removeSingleRepeats(packed);
+
+ int[] matchingBracket = buildMatchingBrackets(packed);
+ int maxFrames = packed.length() + 1;
+ int[] starts = new int[maxFrames];
+ int[] positions = new int[maxFrames];
+ int[] ends = new int[maxFrames];
+ int[] repeatsLeft = new int[maxFrames];
+
+ int top = 0;
+ starts[top] = 0;
+ positions[top] = 0;
+ ends[top] = packed.length();
+ repeatsLeft[top] = 1;
+
+ int matched = 0;
+
+ while (top >= 0 && matched < limit) {
+ if (positions[top] >= ends[top]) {
+ repeatsLeft[top]--;
+
+ if (repeatsLeft[top] > 0) {
+ positions[top] = starts[top];
+ } else {
+ top--;
+ }
+
+ continue;
+ }
+
+ char current = packed.charAt(positions[top]);
+
+ if (current >= 'a' && current <= 'z') {
+ if (base.charAt(matched) != current) {
+ return matched;
+ }
+
+ matched++;
+ positions[top]++;
+ } else {
+ int repeat = current - '0';
+ int openBracket = positions[top] + 1;
+ int closeBracket = matchingBracket[openBracket];
+
+ positions[top] = closeBracket + 1;
+
+ top++;
+ starts[top] = openBracket + 1;
+ positions[top] = openBracket + 1;
+ ends[top] = closeBracket;
+ repeatsLeft[top] = repeat;
+ }
+ }
+
+ return matched;
+ }
+
+ private static String removeSingleRepeats(String packed) {
+ int length = packed.length();
+ boolean hasSingleRepeat = false;
+
+ for (int i = 0; i + 1 < length; i++) {
+ if (packed.charAt(i) == '1' && packed.charAt(i + 1) == '[') {
+ hasSingleRepeat = true;
+ break;
+ }
+ }
+
+ if (!hasSingleRepeat) {
+ return packed;
+ }
+
+ int[] matchingBracket = buildMatchingBrackets(packed);
+ boolean[] skip = new boolean[length];
+
+ for (int i = 0; i + 1 < length; i++) {
+ if (packed.charAt(i) == '1' && packed.charAt(i + 1) == '[') {
+ int openBracket = i + 1;
+ int closeBracket = matchingBracket[openBracket];
+
+ skip[i] = true;
+ skip[openBracket] = true;
+ skip[closeBracket] = true;
+ }
+ }
+
+ StringBuilder normalized = new StringBuilder(length);
+
+ for (int i = 0; i < length; i++) {
+ if (!skip[i]) {
+ normalized.append(packed.charAt(i));
+ }
+ }
+
+ return normalized.toString();
+ }
+
+ private static int[] buildMatchingBrackets(String packed) {
+ int[] matchingBracket = new int[packed.length()];
+ int[] stack = new int[packed.length()];
+ int top = 0;
+
+ for (int i = 0; i < packed.length(); i++) {
+ char current = packed.charAt(i);
+
+ if (current == '[') {
+ stack[top] = i;
+ top++;
+ } else if (current == ']') {
+ top--;
+ int openBracket = stack[top];
+ matchingBracket[openBracket] = i;
+ }
+ }
+
+ return matchingBracket;
+ }
+
+ // -------------------- FAST INPUT --------------------
+ static final class FastIn {
+ private final InputStream in;
+ private final byte[] buf = new byte[1 << 16];
+ private int ptr = 0;
+ private int len = 0;
+
+ FastIn(InputStream in) {
+ this.in = in;
+ }
+
+ private int read() throws IOException {
+ if (ptr >= len) {
+ len = in.read(buf);
+ ptr = 0;
+
+ if (len <= 0) {
+ return -1;
+ }
+ }
+
+ return buf[ptr++];
+ }
+
+ int nextInt() throws IOException {
+ int c;
+
+ do {
+ c = read();
+
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ int sign = 1;
+
+ if (c == '-') {
+ sign = -1;
+ c = read();
+ }
+
+ int val = 0;
+
+ while (c > ' ') {
+ val = val * 10 + (c - '0');
+ c = read();
+ }
+
+ return val * sign;
+ }
+
+ String next() throws IOException {
+ int c;
+
+ do {
+ c = read();
+
+ if (c == -1) {
+ throw new EOFException("Unexpected EOF");
+ }
+ } while (c <= ' ');
+
+ byte[] tmp = new byte[32];
+ int n = 0;
+
+ while (c > ' ') {
+ if (n == tmp.length) {
+ byte[] resized = new byte[tmp.length * 2];
+ System.arraycopy(tmp, 0, resized, 0, tmp.length);
+ tmp = resized;
+ }
+
+ tmp[n] = (byte) c;
+ n++;
+
+ c = read();
+
+ if (c == -1) {
+ break;
+ }
+ }
+
+ return new String(tmp, 0, n);
+ }
+ }
+
+ // -------------------- FAST OUTPUT --------------------
+ static final class FastOut {
+ private final OutputStream out;
+ private final byte[] buf = new byte[1 << 16];
+ private int p = 0;
+
+ FastOut(OutputStream out) {
+ this.out = out;
+ }
+
+ void writeByte(int b) throws IOException {
+ if (p == buf.length) {
+ flush();
+ }
+
+ buf[p] = (byte) b;
+ p++;
+ }
+
+ void writeStringPrefix(String s, int length) throws IOException {
+ for (int i = 0; i < length; i++) {
+ writeByte(s.charAt(i));
+ }
+ }
+
+ void flush() throws IOException {
+ out.write(buf, 0, p);
+ p = 0;
+ }
+ }
+
+ private static void run() throws Exception {
+ FastIn in = new FastIn(System.in);
+ FastOut out = new FastOut(System.out);
+
+ int n = in.nextInt();
+ String first = decodePrefix(in.next(), MAX_UNPACKED_LENGTH);
+ int prefixLength = first.length();
+
+ for (int i = 1; i < n; i++) {
+ if (prefixLength == 0) {
+ break;
+ }
+
+ String packed = in.next();
+ prefixLength = commonPrefixWithPacked(packed, first, prefixLength);
+ }
+
+ out.writeStringPrefix(first, prefixLength);
+ out.writeByte('\n');
+ out.flush();
+ }
+
+ private static void test() {
+ assertEq("aaa", solve(new String[]{
+ "2[a]2[ab]",
+ "3[a]2[r2[t]]",
+ "a2[aa3[b]]"
+ }));
+
+ assertEq("aba", solve(new String[]{
+ "abacabaca",
+ "2[abac]a",
+ "3[aba]"
+ }));
+
+ assertEq("ab", solve(new String[]{
+ "abcde",
+ "ab"
+ }));
+
+ assertEq("", solve(new String[]{
+ "abc",
+ "2[z]"
+ }));
+
+ assertEq("abcccabccc", solve(new String[]{
+ "2[ab3[c]]",
+ "abcccabcccx"
+ }));
+
+ assertEq("abc", solve(new String[]{
+ "1[1[1[abc]]]"
+ }));
+
+ System.out.println("Test OK");
+ }
+
+ private static void assertEq(String expected, String actual) {
+ if (!expected.equals(actual)) {
+ throw new AssertionError("Expected=" + expected + ", actual=" + actual);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (System.getProperty("os.name").startsWith("Windows")) {
+ test();
+ } else {
+ run();
+ }
+ }
+}