45D - Event Dates

This is a slight modification of the classical greedy interval scheduling.

Sort intervals, $l, r$, based on their maximum ending time, $r$. We can greedily pick the $ith$ interval’s position by the first $j \geq l$ that is free.

Time complexity: $O(n \log{n})$

Memory complexity: $O(n)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using iii = tuple<int, int, int>;
using vi = vector<int>;
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int n;
    cin >> n;
    vector<iii> a(n);
    int maxr = 0, i = 0;
    for (auto &[r, l, ix] : a)
    {
        cin >> l >> r;
        ix = i++;
        maxr = max(maxr, r);
    }
    sort(begin(a), end(a));
    vi used(maxr + 1, 0), ans(n);
    for (auto [r, l, i] : a)
    {
        while (used[l])
            l++;
        ans[i] = l;
        used[l] = true;
    }
    for (auto x : ans)
        cout << x << " ";
    cout << endl;
    return 0;
}


1473D - Program

For a given query $(l, r)$, we are dividing the string into three parts:

  • left: $(1, l - 1)$
  • mid: $(l, r)$
  • right: $(r + 1, n)$

Note that removing mid will essentially decrease right by the net change in mid.

Furthermore, note that for any range $l, r$, we may know the number of unique elements in that range by finding the maximum and minimum on that range (since every movement either increases or decreases by 1).

This suggests a data structure problem we can solve using segment tree.

For each range, store the maximum, minimum and the net change and query the three ranges described beforehand.

Reverse the net change of the mid range to the right range (by subtracting it from right’s min and max).

Finally, merge the left range with the right range if they overlap, otherwise count them separately. If they don’t contain $0$ increase the answer by $1$.

Time complexity: $O(m \log{n})$

Memory complexity: $O(n)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
using pll = pair<ll, ll>;
ll const INF = 1e15;
struct S
{
    ll mn, mx, delta;
};
S op(S a, S b) { return {min(a.mn, b.mn), max(a.mx, b.mx), a.delta + b.delta}; }
S e() { return {INF, -INF, 0}; };
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int t;
    cin >> t;
    while (t--)
    {
        int n, m;
        cin >> n >> m;
        string s;
        cin >> s;
        using SegmentTree = atcoder::segtree<S, op, e>;
        vector<S> a(n);
        int cur = 0, delta;
        for (int i = 0; i < n; ++i)
        {
            delta = (s[i] == '+' ? +1 : -1);
            cur += delta;
            a[i] = {cur, cur, delta};
        }
        SegmentTree st(a);
        auto intersect = [](pll a, pll b) {
            if (a.first > b.first)
                swap(a, b);
            return a.second >= b.first;
        };
        while (m--)
        {
            int l, r;
            cin >> l >> r, l--;
            S left = st.prod(0, l), mid = st.prod(l, r), right = st.prod(r, n);
            vector<pll> intervals;
            if (left.mn != INF)
                intervals.emplace_back(left.mn, left.mx);
            if (right.mn != INF)
                intervals.emplace_back(right.mn - mid.delta,
                                       right.mx - mid.delta);
            ll ans = 0;
            if (none_of(begin(intervals), end(intervals),
                        [](ii lr) { return lr.first <= 0 and 0 <= lr.second; }))
                ans += 1;
            if (intervals.size() == 2 and intersect(intervals[0], intervals[1]))
            {
                intervals[0] = {min(intervals[0].first, intervals[1].first),
                                max(intervals[0].second, intervals[1].second)};
                intervals.pop_back();
            }
            for (auto [ll, rr] : intervals)
                ans += rr - ll + 1;
            cout << ans << endl;
        }
    }
    return 0;
}


1473C - No More Inversions

This comment explained it well.

Time complexity: $O(n)$

Memory complexity: $O(1)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int t;
    cin >> t;
    while (t--)
    {
        int n, k;
        cin >> n >> k;
        for (int i = 1; i <= 2 * k - n - 1; ++i)
            cout << i << " ";
        for (int i = k; i >= 2 * k - n; --i)
            cout << i << " ";
    }
    return 0;
}


1473B - String Lcm

Note that our lcm string, has to have size $lcm(a, b)$. Let’s just extend both $a$ and $b$ so that they have that size and then compare them.

Time complexity: $O(n)$

Memory complexity: $O(n)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int t;
    cin >> t;
    while (t--)
    {
        string a, b;
        cin >> a >> b;
        int n = a.size(), m = b.size(), l = lcm(n, m);
        a.resize(l), b.resize(l);
        for (int i = n; i < l; ++i)
            a[i] = a[i % n];
        for (int i = m; i < l; ++i)
            b[i] = b[i % m];
        cout << (a == b ? a : "-1") << endl;
    }
    return 0;
}


1473A - Replacing Elements

If all elements are less than $d$, then no change is need to be made. Assume otherwise.

Note that the optimal strategy is to pick the two smallest values as $a_j$ and $a_k$ and replace every $a_i > a_j + a_k$ with their sum. Iff $a_j + a_k \leq d$ then it is possible.

Time complexity: $O(n \log{n})$

Memory complexity: $O(n)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
bool solve(vi a, int d)
{
    sort(begin(a), end(a));
    if (a.back() <= d)
        return true;
    if (a.size() > 1 and a[0] + a[1] <= d)
        return true;
    return false;
}
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int t;
    cin >> t;
    while (t--)
    {
        int n, d;
        cin >> n >> d;
        vi a(n);
        for (auto &ai : a)
            cin >> ai;
        cout << (solve(a, d) ? "YES" : "NO") << endl;
    }
    return 0;
}


1367C - Social Distance

For each block of consecutive zeros (with one on both ends) of size $m$, one can place $ceil(\frac{m - 2k}{k + 1})$ ones.

Time complexity: $O(n)$

Memory complexity: $O(n)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
int ceil(int a, int b) { return (a + b - 1) / b; }
int solve(string s, int k)
{
    s = "1" + string(k, '0') + s + string(k, '0') + "1";
    int cnt = 0;
    vi lengths;
    for (auto c : s)
    {
        if (c == '0')
            ++cnt;
        else
        {
            lengths.push_back(max(cnt - 2 * k, 0));
            cnt = 0;
        }
    }
    return accumulate(begin(lengths), end(lengths), 0LL, [k](int acc, int x) {
        return acc + ceil(x, k + 1);
    });
}
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int t;
    cin >> t;
    while (t--)
    {
        int n, k;
        string s;
        cin >> n >> k >> s;
        cout << solve(s, k) << endl;
    }
    return 0;
}


1368D - And Or And Square Sum

To maximize each term of the summation, we should try to make large numbers as large as possible (to maximize the contribution of each $a_i^2$).

Note that each operation effectively transfers all $1$’s from a number $a_i$ to another number $a_j$ (in binary).

We can think of a greedy strategy where we redistribute the $1$’s and concentrate them in a few numbers in order to make them larger.

Time complexity: $O(n \log{a_{max}})$

Memory complexity: $O(n)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
ll solve(vi a)
{
    int const BSZ = 20;
    vi cnt(BSZ, 0);
    for (auto ai : a)
        for (int i = 0; i < BSZ; ++i)
            cnt[i] += (ai >> i) & 1;
    vector<ll> ans(a.size(), 0);
    for (auto &ai : ans)
    {
        for (int i = 0; i < BSZ; ++i)
        {
            if (cnt[i])
            {
                ai |= (1 << i);
                cnt[i]--;
            }
        }
    }
    transform(begin(ans), end(ans), begin(ans), [](ll x) { return x * x; });
    return accumulate(begin(ans), end(ans), 0LL);
}
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int n;
    cin >> n;
    vi a(n);
    for (auto &ai : a)
        cin >> ai;
    cout << solve(a) << endl;
    return 0;
}


1368C - Even Picture

Editorial.

Time complexity: $O(n)$

Memory complexity: $O(1)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int n;
    cin >> n;
    vector<ii> points = {{1, 0}, {0, 1}, {1, 1}};
    cout << (n + 1) * 3 + 1 << endl;
    cout << 0 << " " << 0 << endl;
    for (int i = 0; i <= n; ++i)
        for (auto &[x, y] : points)
            cout << x++ << " " << y++ << endl;
    return 0;
}


1401D - Maximum Distributed Tree

Let cnt(u, v), be the number of paths that cross edge $(u, v)$. The idea is to assign the largest primes to the edges with largest cnt.

Implementation details are described in Editorial. But the key idea is that if $m < n - 1$, we fill the remaining with $1$s and otherwise we merge the biggest primes into one.

Time complexity: $O(n \log{n})$

Memory complexity: $O(n)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
using Graph = vector<vi>;
struct DFS
{
    const Graph &g;
    function<void(int)> fu0, fu1;
    function<void(int, int)> fuv0, fuv1;
    DFS(const Graph &g) : g(g) { reset(); }
    void traverse(int u, int p = -1)
    {
        fu0(u);
        for (auto v : g[u])
        {
            if (v == p)
                continue;
            fuv0(u, v);
            traverse(v, u);
            fuv1(u, v);
        }
        fu1(u);
    }
    void operator()(int u, int p = -1) { traverse(u, p); }
    void reset()
    {
        fu0 = fu1 = [](int u) { (void)u; };
        fuv0 = fuv1 = [](int u, int v) { (void)u, (void)v; };
    }
};
vector<ll> get_edge_counts(const Graph &g)
{
    int n = (int)(g).size();
    vector<ll> ans(n, 0), sz(n, 1);
    DFS dfs(g);
    dfs.fuv1 = [&sz](int u, int v) { sz[u] += sz[v]; };
    dfs.fu1 = [&sz, &ans, n](int u) { ans[u] = (n - sz[u]) * sz[u]; };
    dfs(0);
    return ans;
}
int solve(const Graph &g, vector<ll> p)
{
    using mint = atcoder::modint1000000007;
    int n = g.size(), m = p.size();
    auto cnt = get_edge_counts(g);
    swap(cnt.front(), cnt.back()), cnt.pop_back();
    sort(begin(cnt), end(cnt), greater<ll>());
    sort(begin(p), end(p), greater<ll>());
    if (m <= n - 1)
        p.resize(n - 1, 1);
    else
    {
        auto end = begin(p) + m - n + 1;
        p[m - n + 1] *=
            accumulate(begin(p), end, mint(1), multiplies<mint>()).val();
        p.erase(begin(p), end);
    }
    mint ans = 0;
    for (int i = 0; i < n - 1; ++i)
        ans += mint(p[i]) * cnt[i];
    return ans.val();
}
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int t;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        Graph g(n);
        for (int i = 0; i < n - 1; ++i)
        {
            int u, v;
            cin >> u >> v, u--, v--;
            g[u].push_back(v);
            g[v].push_back(u);
        }
        int m;
        cin >> m;
        vector<ll> p(m);
        for (auto &pi : p)
            cin >> pi;
        cout << solve(g, p) << endl;
    }
    return 0;
}


1281E - Jeremy Bearimy

Editorial.

Time complexity: $O(n + m)$

Memory complexity: $O(n + m)$

Click to show code.


using namespace std;
using ll = long long;
using ii = pair<int, int>;
using vi = vector<int>;
using Graph = vector<vector<ii>>;
pair<ll, ll> solve(Graph g)
{
    int n = g.size();
    vector<ll> sz(n, 1), dp1(n, 0), dp2(n, 0);
    function<void(int, int)> dfs;
    dfs = [&](int u, int p) {
        for (auto [v, w] : g[u])
        {
            if (v == p)
                continue;
            dfs(v, u);
            sz[u] += sz[v];
            dp1[u] += dp1[v] + (sz[v] & 1 ? w : 0);
            dp2[v] = min(sz[v], n - sz[v]) * w;
        }
    };
    dfs(0, 0);
    return {dp1[0], accumulate(begin(dp2), end(dp2), 0LL)};
}
int main(void)
{
    ios::sync_with_stdio(false), cin.tie(NULL);
    int t;
    cin >> t;
    while (t--)
    {
        int k;
        cin >> k;
        Graph g(2 * k);
        for (int i = 0; i < 2 * k - 1; ++i)
        {
            int u, v, w;
            cin >> u >> v >> w, u--, v--;
            g[u].emplace_back(v, w);
            g[v].emplace_back(u, w);
        }
        auto [G, B] = solve(g);
        cout << G << " " << B << endl;
    }
    return 0;
}