Блог пользователя grecil

Автор grecil, история, 4 недели назад, По-английски

For the longest time, I only knew how to use the lazy segtree for Range Add / Range Sum queries. It would take me painstakingly long to solve problems which had lazy propagation in a slightly different form. I then discovered the Atcoder Lazy Segment Tree template (C++ / Python), which would allow you to solve any Lazy Propagation problem by just defining 6 mathematical parameters: S, op, mapping, composition, e and id. This blog is an attempt to explain the math behind Lazy Propagation in an intuitive manner using Socratic dialogue. I would recommend that you guys ponder over the questions before you read the answers.

Prerequisites: You know how a standard segment tree works. You know how to build it, how to perform point updates, and how to query a range. If you don't, close this tab, learn that, and come back.

Section 1: The Art of Procrastination

Question: A standard segment tree processes a point update in $$$O(\log N)$$$. If a query requires adding a value $$$v$$$ to every element in the range [0, 50000], why can't we simply call the point update function in a loop?

Answer

Question: To achieve $$$O(\log N)$$$ updates, we must stop tree traversal early. How do we defer these updates mathematically without losing the data?

Answer

Question: If a parent node defers updating its children, what is the exact trigger that forces it to finally push the updates down? Define the golden rule of lazy propagation and the exact steps of the push_down function.

Answer

Section 2: Formalizing the Mathematics

Question: If a node defers an update and does not visit its children, how does it compute its own correct sum for future queries? If a node represents a segment of $$$L$$$ elements and a Range Add $$$v$$$ operation arrives, what exact state variables must the node store?

Answer

Question: Formally define the mapping(f, S) function. For a Range Add / Range Sum tree, if a tag $$$v$$$ arrives at a node with state $$$S$$$, what is the exact algebraically updated state?

Answer

Question: Queuing multiple pending operations on a single node destroys the $$$O(1)$$$ push-down complexity. If a node holding tag $$$g$$$ is hit by a new tag $$$f$$$, define the composition(f, g) function for a Range Add tree to compress them instantly.

Answer

Question: That handles the top-down updates. How do we compute a parent's state from its children during the bottom-up phase of the traversal? Define the op(left, right) function for a Range Sum tree.

Answer

Question: To implement this cleanly, segment trees usually pad out-of-bounds queries or empty nodes with mathematical identities. Define the identity state e() and the identity operation id() for our Range Add / Range Sum tree.

Answer

Section 3: Letting Algebra Dictate the Architecture

Once the mechanics of S, op, mapping, composition, e, and id are strictly defined, the tree traversal logic is permanently closed. You never modify it again. Every lazy segment tree problem from this point forward is strictly a mathematical exercise in defining these six objects.

We will prove this by escalating the mathematical complexity.

Case Study 1: Range Affine, Range Sum

The Problem: You must support two completely different range updates on the same tree: "Add $$$v$$$ to all elements in range" and "Multiply all elements in range by $$$v$$$".

Attempting to track the timeline of different operations via separate variables (e.g., add_val, mult_val) and boolean flags leads to catastrophic edge cases during push_down. Instead of tracking timelines, we must generalize the operation itself.

Question: How can you represent both "Add $$$v$$$" and "Multiply by $$$v$$$" as a single, unified mathematical operation?

Answer

Question: If the state is $$$S = (\text{sum}, \text{length})$$$ and the arriving tag is $$$F = (a, b)$$$, what is the exact algebraic formula for mapping(F, S)?

Answer

Question: If a node holds a pending tag $$$g(x) = a_2 x + b_2$$$, and a new tag $$$f(x) = a_1 x + b_1$$$ arrives, how do we compress them? Define composition(f, g) and the identity id().

Answer

Case Study 2: Range Add, Range Sum of Squares

The Problem: The operation is a simple Range Add $$$v$$$, but the query asks for the Sum of Squares of all elements in a range.

You cannot simply store a sq_sum variable and expect it to update correctly. The query dictates the state. We must let the binomial expansion dictate the exact variables our struct requires.

Question: If a single element $$$x$$$ is updated by adding $$$v$$$, the new square is $$$(x + v)^2$$$. Expand this mathematically across an entire segment of length $$$L$$$. Based on the resulting terms, what exact variables must your state $$$S$$$ store to compute the new sum of squares in $$$O(1)$$$ time?

Answer

Question: Given the state $$$S = (\text{sq_sum}, \text{sum}, \text{length})$$$, define the exact mapping(v, S) function when a tag $$$v$$$ arrives.

Answer

Case Study 3: Range Flip, Range Inversions

The Problem: You are given a binary array consisting only of $$$0s$$$ and $$$1s$$$. You must support two operations:

  1. Range Flip: Flip all bits in a range ($$$0 \to 1$$$, $$$1 \to 0$$$).
  2. Range Inversions: Count the number of inversions in a range. An inversion is a pair of indices $$$(i, j)$$$ such that $$$i \lt j$$$ and $$$A[i] \gt A[j]$$$ (meaning $$$A[i] = 1$$$ and $$$A[j] = 0$$$).

Question: To support calculating inversions across ranges, what exact variables must the state $$$S$$$ store, and how is the horizontal merge op(left, right) defined?

Answer

Question: Our lazy tag $$$F$$$ is a boolean flag where true means "flip the segment". How do we define mapping(F, S) to instantly calculate the new number of inversions when a flip arrives?

Answer

Question: If a node holds a pending flip tag $$$g$$$, and a new flip tag $$$f$$$ arrives, how do we define composition(f, g) and the identity id()?

Answer

For more problems, you can visit Codeforces Edu Section — Segment Tree, part 2

Полный текст и комментарии »

  • Проголосовать: нравится
  • +21
  • Проголосовать: не нравится

Автор grecil, история, 11 месяцев назад, По-английски

Problems involving movement on a grid can be solved using tuples/pairs of the form (x, y). However, doing addition/subtraction of pairs/tuples is not supported by default. One alternative to this is using complex numbers.

Using complex numbers for geometry is not a new idea. You can read more about it in this blog. I will not go into the more "geometric" side of things. My aim here is to make you guys feel comfortable with using complex numbers instead of pair/tuples in grid-related problems.

I code in Python and for the better understanding of readers, I used AI to convert those codes to C++. We have to keep few things in mind when using std::complex in C++. Certain methods such as std::polar() and std::abs() don't work when you use complex with integral data types (int, long long etc.). They only work with FP datatypes (float, double, long double etc.). You can still use std::complex with integral datatypes if the use case is limited to simple arithmetic functions like addition, subtraction and multiplication.

Despite all of their benefits, complex numbers have one flaw. They cannot be compared directly. Neither do they have std::hash in C++. If you want to use std::map/std::set, you have to implement a ComplexLess operator. If you want to use std::unordered_map/std::unordered_set, you have to implement ComplexHash function. I have provided sample templates for both. I would recommend not using unordered_map and unordered_set unless you have randomized the hash function.

#include <bits/stdc++.h>
using namespace std;

template <typename T>
struct ComplexLess {
    bool operator()(const complex<T>& a, const complex<T>& b) const noexcept {
        if (a.real() != b.real()) return a.real() < b.real();
        return a.imag() < b.imag();
    }
};

template <typename T>
struct ComplexHash {
    size_t operator()(const complex<T>& c) const noexcept {
        size_t h1 = hash<T>{}(c.real());
        size_t h2 = hash<T>{}(c.imag());
        return h1 ^ (h2 << 1);
    }
};

In Python, the hash of a complex number is defined. If you want to compare two complex numbers, you can implement a class of your own with required dunder methods. Or you can make a simple lambda function and use it as key inside inbuilt methods like sort, max, min etc.

ComplexLess = lambda a, b: a.real < b.real if a.real != b.real else a.imag < b.imag

or

ComplexLess = lambda a, b: (a.real, a.imag) < (b.real, b.imag)

Now let's solve some problems using this technique. You can try them out for yourself before reading my solutions.

1) CF 626A — Robot Sequence

This problem asks how many contiguous command subsequences bring the robot back to its starting position. The key insight is that if the robot reaches the same position twice during execution, the commands between those positions form a closed loop.

We track the robot's cumulative position after each command using coordinates (treating movements as complex numbers for simplicity). If position P appears k times in our sequence, we can choose any 2 occurrences as start/end points for a valid loop, giving us k*(k-1)/2 possible subsequences.

The solution computes prefix sums of movements, counts frequency of each position (including initial position 0), then applies the combinatorial formula C(k,2) for each position's count. This efficiently counts all valid returning subsequences without explicitly checking each one.

Python

from collections import Counter
from itertools import accumulate

d = {"L": -1, "R": 1, "U": 1j, "D": -1j}
n = int(input())
c = Counter(accumulate(d[i] for i in input()))
c[0] += 1
print(sum((i * i - i) // 2 for i in c.values()))

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ci = complex<ll>;

template <typename T>
struct ComplexLess {
    bool operator()(const complex<T>& a, const complex<T>& b) const noexcept {
        if (a.real() != b.real()) return a.real() < b.real();
        return a.imag() < b.imag();
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    string s;
    cin >> s;

    map<char, ci> d = {
        {'L', ci(-1,  0)},
        {'R', ci( 1,  0)},
        {'U', ci( 0,  1)},
        {'D', ci( 0, -1)}
    };

    map<ci, ll, ComplexLess<ll>> cnt;
    ci pos{0, 0};
    cnt[pos] = 1;

    for (char mv : s) {
        pos += d[mv];
        ++cnt[pos];
    }

    ll ans = 0;
    for (auto const& [p, c] : cnt) {
        ans += c * (c - 1) / 2;
    }

    cout << ans << '\n';
    return 0;
}

2) ABC 398D — Bonfire

This problem simulates smoke spreading from a campfire that gets blown by wind, with new smoke generated at the origin whenever it becomes empty. We need to determine when smoke reaches a target position after each time step.

The key insight is that smoke at any position P will reach target position (R,C) if and only if there was previously smoke at position (P — (R,C)). Since we track all cumulative wind displacements, we can check if the "reverse displacement" needed to reach our target has occurred before.

At each time step, we compute the cumulative displacement from wind, then check if smoke existed at the position that would blow to our target. We maintain a set of all positions where smoke has been generated (starting with origin), and for each new wind displacement, we check if (displacement — target) exists in our set before adding the current displacement.

Python

from itertools import accumulate

d = {"E": 1, "W": -1, "S": 1j, "N": -1j}
n, r, c = map(int, input().split())
x = c + r * 1j
arr = list(accumulate(d[i] for i in input()))
seen = set([0])
ans = []
for i in arr:
    ans.append(1 if i - x in seen else 0)
    seen.add(i)
print(*ans, sep="")

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ci = complex<ll>;

template <typename T>
struct ComplexLess {
    bool operator()(const complex<T>& a, const complex<T>& b) const noexcept {
        if (a.real() != b.real()) return a.real() < b.real();
        return a.imag() < b.imag();
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    ll r, c;
    cin >> n >> r >> c;

    ci target(c, r);

    string s;
    cin >> s;

    map<char, ci> d = {
        {'E', ci(1,  0)},
        {'W', ci(-1, 0)},
        {'S', ci(0,  1)},
        {'N', ci(0, -1)}
    };

    vector<ci> arr;
    arr.reserve(n);
    ci pos(0, 0);
    for (char mv : s) {
        pos += d[mv];
        arr.push_back(pos);
    }

    set<ci, ComplexLess<ll>> seen;
    seen.insert(ci(0, 0));

    string ans;
    ans.reserve(n);
    for (ci& p : arr) {
        ci diff = p - target;
        ans.push_back(seen.count(diff) ? '1' : '0');
        seen.insert(p);
    }

    cout << ans;
    return 0;
}

3) CF 1296C — Yet Another Walking Robot

This problem asks us to find the shortest substring we can remove from a robot's path such that the robot still ends at the same final position. The key insight is that removing a substring between positions i and j preserves the endpoint if and only if the robot is at the same coordinate at both positions i and j.

We track the robot's cumulative position after each move using prefix sums. If the robot reaches the same position at two different times, then all the moves between those times form a closed loop that can be removed without affecting the final destination.

The solution computes prefix positions and uses a map to track the most recent occurrence of each coordinate. When we encounter a position we've seen before, we calculate the length of the substring between the two occurrences and update our answer if this gives us a shorter removable segment.

Python

from itertools import accumulate

d = {"L": -1, "R": 1, "U": 1j, "D": -1j}
for _ in range(int(input())):
    n = int(input())
    arr = [0] + list(accumulate(d[i] for i in input()))
    last = {}
    mn = float("inf")
    ans = -1
    for i, val in enumerate(arr):
        if val in last:
            if i - last[val] < mn:
                mn = i - last[val]
                ans = (last[val] + 1, i)
        last[val] = i
    if ans == -1:
        print(-1)
    else:
        print(*ans)

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ci = complex<ll>;

template <typename T>
struct ComplexLess {
    bool operator()(const complex<T>& a, const complex<T>& b) const noexcept {
        if (a.real() != b.real()) return a.real() < b.real();
        return a.imag() < b.imag();
    }
};


int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T;
    cin >> T;

    map<char, ci> d = {
        {'L', ci(-1,  0)},
        {'R', ci( 1,  0)},
        {'U', ci( 0,  1)},
        {'D', ci( 0, -1)}
    };

    while (T--) {
        int n;
        cin >> n;
        string s;
        cin >> s;

        vector<ci> arr(n + 1);
        arr[0] = {0, 0};
        for (int i = 0; i < n; ++i) {
            arr[i + 1] = arr[i] + d[s[i]];
        }

        map<ci, int, ComplexLess<ll>> last;
        int mn = INT_MAX;
        pair<int,int> ans = {-1, -1};

        for (int i = 0; i <= n; ++i) {
            ci pos = arr[i];
            auto it = last.find(pos);
            if (it != last.end()) {
                int prev = it->second;
                int len = i - prev;
                if (len < mn) {
                    mn = len;
                    ans = {prev + 1, i};
                }
            }
            last[pos] = i;
        }

        if (ans.first == -1) {
            cout << -1 << '\n';
        } else {
            cout << ans.first << ' ' << ans.second << '\n';
        }
    }

    return 0;
}

4) CF 1073C — Vasya and Robot

This problem asks us to find the minimum length contiguous subsegment of robot commands that we must modify to reach a target position (x, y). The key insight is that we can replace any subsegment of length m with arbitrary commands, so we need to check if m moves can cover the "correction" needed.

First, we check if the problem is solvable: the Manhattan distance to target must not exceed n moves, and the parity must match (since each move changes Manhattan distance by ±2 or 0, we need the difference between target distance and available moves to be even).

For any subsegment of length m starting at position i, we calculate what displacement is needed after removing the original subsegment and adding our target displacement. If this required displacement has Manhattan distance ≤ m, then we can construct valid moves to achieve it within the subsegment.

The solution uses binary search on the subsegment length, checking each candidate length by testing all possible starting positions. We compute prefix sums of movements to efficiently calculate the displacement that would remain after removing any subsegment, then verify if the correction can be made within the allowed number of moves.

Python

from itertools import accumulate


def check(m):
    for i in range(n - m + 1):
        k = arr[i + m] - arr[i] + need
        if abs(k.real) + abs(k.imag) <= m:
            return True
    return False


d = {"L": -1, "R": 1, "U": 1j, "D": -1j}
n = int(input())
arr = [0] + list(accumulate(d[i] for i in input()))
x, y = map(int, input().split())
need = (x + y * 1j) - arr[-1]
if abs(x) + abs(y) > n or (abs(x) + abs(y) - n) % 2:
    print(-1)
else:
    hi, lo = n, 0
    while lo <= hi:
        mid = (hi + lo) // 2
        if check(mid):
            hi = mid - 1
        else:
            lo = mid + 1
    print(lo)

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ci = complex<ll>;

bool check_complex(const vector<ci>& arr, const ci& need, int m) {
    int n = arr.size() - 1;
    for (int i = 0; i + m <= n; ++i) {
        ci k = arr[i + m] - arr[i] + need;
        ll dist = llabs(k.real()) + llabs(k.imag());
        if (dist <= m) return true;
    }
    return false;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;
    string s;
    cin >> s;

    ll x, y;
    cin >> x >> y;

    vector<ci> arr(n + 1);
    arr[0] = {0, 0};
    map<char, ci> d = {
        {'L', ci(-1,  0)},
        {'R', ci( 1,  0)},
        {'U', ci( 0,  1)},
        {'D', ci( 0, -1)}
    };
    for (int i = 0; i < n; ++i) {
        arr[i + 1] = arr[i] + d[s[i]];
    }

    ci need = ci(x, y) - arr[n];
    ll manh = llabs(x) + llabs(y);
    if (manh > n || ((manh - n) & 1)) {
        cout << -1;
        return 0;
    }

    int lo = 0, hi = n;
    while (lo <= hi) {
        int mid = (lo + hi) / 2;
        if (check_complex(arr, need, mid)) hi = mid - 1;
        else lo = mid + 1;
    }
    cout << lo;
    return 0;
}

5) CF 1902D — Robot Queries

This problem asks whether a robot visits a target point when executing commands with a specific substring reversed. The key insight is that reversing a substring splits the path into three segments: prefix (before reversal), reversed middle section, and suffix (after reversal).

We precompute prefix sums for both the original sequence and its complete reverse to handle these segments efficiently. For any query with reversal range [l, r], we check two cases: either the target is reached in the unchanged portions (prefix or suffix), or it's reached within the reversed middle section.

For the first case, we check if the target position appears in our original prefix sums outside the reversal range. For the second case, we calculate the position offset needed to account for the path change caused by reversal, then check if the adjusted target appears in our reversed sequence's prefix sums within the appropriate time window.

Python

from itertools import accumulate
from collections import defaultdict
from bisect import bisect_left as bsl

d = {"L": -1, "R": 1, "U": 1j, "D": -1j}
n, q = map(int, input().split())
s = input()
arr = [0, *accumulate(d[i] for i in s)]
brr = [0, *accumulate(d[i] for i in s[::-1])]
da = defaultdict(list)
db = defaultdict(list)
for i in range(n + 1):
    da[arr[i]].append(i)
    db[brr[i]].append(i)
for i in range(q):
    x, y, l, r = map(int, input().split())
    off = brr[n - r] - arr[l - 1]
    dest = x + y * 1j
    if dest in da and (da[dest][0] < l or da[dest][-1] > r):
        print("YES")
    elif dest + off in db:
        pos = bsl(db[dest + off], n - r + 1)
        if pos < len(db[dest + off]):
            if db[dest + off][pos] <= n - l + 1:
                print("YES")
            else:
                print("NO")
        else:
            print("NO")
    else:
        print("NO")

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ci = complex<ll>;

template <typename T>
struct ComplexLess {
    bool operator()(const complex<T>& a, const complex<T>& b) const noexcept {
        if (a.real() != b.real()) return a.real() < b.real();
        return a.imag() < b.imag();
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, q;
    cin >> n >> q;
    string s;
    cin >> s;

    map<char, ci> d = {
        {'L', ci(-1,  0)},
        {'R', ci( 1,  0)},
        {'U', ci( 0,  1)},
        {'D', ci( 0, -1)}
    };

    vector<ci> arr(n+1);
    arr[0] = {0, 0};
    for (int i = 0; i < n; ++i) {
        arr[i+1] = arr[i] + d[s[i]];
    }

    vector<ci> brr(n+1);
    brr[0] = {0, 0};
    for (int i = 1; i <= n; ++i) {
        char mv = s[n - i];
        brr[i] = brr[i-1] + d[mv];
    }

    map<ci, vector<int>, ComplexLess<ll>> da, db;
    for (int i = 0; i <= n; ++i) {
        da[arr[i]].push_back(i);
        db[brr[i]].push_back(i);
    }

    while (q--) {
        ll x, y;
        int l, r;
        cin >> x >> y >> l >> r;

        ci dest(x, y);

        ci off = brr[n-r] - arr[l-1];

        auto it_da = da.find(dest);
        if (it_da != da.end()) {
            const auto &v = it_da->second;
            if (v.front() < l || v.back() > r) {
                cout << "YES\n";
                continue;
            }
        }

        ci key = dest + off;
        auto it_db = db.find(key);
        if (it_db != db.end()) {
            const auto &v = it_db->second;
            int lo = n - r + 1;
            int hi = n - l + 1;
            auto pos = lower_bound(v.begin(), v.end(), lo);
            if (pos != v.end() && *pos <= hi) {
                cout << "YES\n";
                continue;
            }
        }

        cout << "NO\n";
    }

    return 0;
}

If you have any doubts or suggestions, please drop them in the reply section. Upvote if you found this helpful or interesting.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +79
  • Проголосовать: не нравится

Автор grecil, история, 12 месяцев назад, По-английски

Recently I came across several problems on different platforms that use the seemingly obscure—but actually very intuitive—idea of “Ternary Mask DP.” It’s just like bitmask DP, but instead of each bit representing two states, each digit represents three: 0, 1, or 2. The core idea is simple: convert a number into base 3, so each digit is in {0, 1, 2}. You can generalize this to any k states by using base-k.

Here’s a generalized function that encodes any integer n into an m-digit list in base k. It returns a little‑endian array of integers, but you can easily modify it to produce a string or even pack the digits into a base‑10 integer. Reverse the output if you prefer big‑endian order. (See Endianness.)

def encode_base_k(n, m, k):
    nums = []
    while n:
        n, r = divmod(n, k)
        nums.append(r)
    return nums + [0] * (m - len(nums))

Below are three examples.

1) ABC 404 D – Goin’ to the Zoo

Solution
Code

2) LC 1931 – Painting a Grid With Three Different Colors

Solution
Code

3) CF Gym 104493 A — Gym Plates

Solution
Code

Ternary (and, more generally, base‑k) mask DP lets you pack multi‑state decisions into a single integer, iterate cleanly over all possibilities, and handle compatibility with simple digit‑by‑digit checks. It’s a powerful pattern for grids, colorings, tilings, and any situation where each element has a few discrete states.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +19
  • Проголосовать: не нравится

Автор grecil, история, 14 месяцев назад, По-английски

Some background —

I use Python for CP and some benefits of using Python are clear and concise syntax, tons of in-built function, dynamic typing, FP-like operations (map, filter, accumulate etc.), list comprehensions, bool-to-int conversions and so on. These features make code-golfing very fun with Python. I discovered jahnvisahni98 in the solutions section of some problem few weeks ago (you can sort solutions by their size). If I find some user frequenting the section, I usually befriend them on Codeforces and even send a message saying I appreciate them. But this case was different. The solution's logic was still long, but the syntax had been shortened by giving a tab space of 1 character. I looked through some of her other solutions and she had a different code style in all of them. Somewhere she had formatted code with long variable names while at other places she had short undecipherable variable names with very poor formatting. This was still not a strong enough evidence.

Fast forward to today, her submissions on 2078C - Breach of Faith speak volumes. Within 2 minutes of submitting problem B, she submitted an O(n^2) solution to C 309795444 that did not even pass sample testcase. Then 8 minutes later, a completely different approach and still fails to pass sample testcase 309803089. 1 minute later, a similar approach but fails sample testcase 309803977. 10 minutes later, she is back with a new approach but this time the code is almost undecipherable 309811178. Fails on the sample testcase regardless.

One minute later, she has already solved D 309811985.

Now back to C. She switches her coding style from undecipherable to decipherable again and submits another testcase 1 failure 309816146 7 minutes later. 1 minute later, she submits another test case 1 failure 309816146.

Now comes the interesting part. 6 minutes later, she submits a poorly formatted solution to 2078F - Binary Subsequence Value Sum which passes all test cases. I do have an explanation for these strange code style change.

She ends her day with another submission on C 4 minutes later, which you can guess, failed on sample testcase 309822692.

My Opinion/Explanation —

This is a classic case of cheating with the help of LLMs. The frequent changes in code style can be explained by the change in LLMs. The poorly formatted short variable name code is a result of her prompting the LLM to make the code look messy and asking it to use shorter variable names with no comments. I will let MikeMirzayanov and the Codeforces community be the judge in this case. I hold no personal grudges. I don't even know her. I found clear evidence of cheating from an account and decided to report it.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +30
  • Проголосовать: не нравится