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

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

For around the past 2-3 weeks, kenkoooo, the website we use for AtCoder problems, hasn't been working.

ABC432 and 433 have been 'registered' by the site, but their problems don't appear. ABC 434 and 435 don't show up at all. There was also an ARC that occured that hasn't been updated on here.

Has there been a change in AtCoder's API that might have caused this? kenkoooo, could you please let us know what's going on?

There also seem to be GitHub issues reporting the same problem:

…but none of them have received a response yet.

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

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

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

Since I couldn't find a tutorial on codeforces (or online), I decided to make a blog.

How do we solve MST Edge Check? I think this is one of the newer added problems (+100 from 300 to 400) judging by the number of solves.

If anyone could provide their solution, I'd be very grateful. Thank you!

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

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

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

OI Checklist – Two Weeks of New Features!

It’s been about two weeks since I first announced OI Checklist in this blog post. The blog was very well received, and I’d like to thank you all for that! In fact, since launch OI Checklist has already grown to 2,261 problems across 18 olympiads and is being used by 214 users. (If you haven’t tried it yet, you can check out the demo mode to see how it works without signing up.)

Over these two weeks, I’ve been working on OI Checklist and added a lot of new features: some functional, some UI/UX improvements, and some smaller tweaks. Here’s a summary of the most important ones:


qoj.ac Sync — Fully Supported

Full Checklist

That’s right! You can now sync with both oj.uz and qoj.ac.

The cookie pulls your problem scores and updates your checklist. Your score for a problem is set to the maximum of what it previously was and the score, fully subtask stitched, that you have on qoj.ac.

Virtual Contests

The UI for virtual contests has also been updated. It now tracks submissions across both oj.uz and qoj.ac and takes the maximum score per subtask, even if the submissions are from different platforms!

This means two things:
- You can now autotrack contests where not all problems are on oj.uz.
- You can freely mix submissions between judges.

Updated interface when starting a virtual contest:

The blue squares indicate that the problem is available on that judge. In this case, the single black square means Problem 4 isn’t available on oj.uz. For all problems with blue squares, submissions made on any of the listed judges will be tracked.


Improved Post-Contest Statistics

If you choose to autotrack submissions for a contest, you’ll be greeted with two feature-rich graphs:

  • A problem-by-problem score graph (toggleable per problem).
  • A total score graph.

Below that, you’ll also see a submission timeline:

Hovering over a point shows how many points that submission gained you. Submissions from both oj.uz and qoj.ac are merged here.


Notes System for Problems

You can now add notes to problems with an editor that supports both Markdown and LaTeX.

Keyboard shortcuts:
- Ctrl/Cmd + B → bold
- Ctrl/Cmd + I → italic
- Ctrl/Cmd + K → hyperlink
- Ctrl/Cmd + Ecode block

The rightmost icon opens a preview, rendering your Markdown into a clean, readable format. You can also use Ctrl/Cmd + / to toggle between edit and preview mode.


Platform preference

Reorder platforms (here) to control which source is used for problem links. The list is checked top to bottom, and the first matching source provides the link.

For example, if you prefer qoj.ac over oj.uz, just drag qoj.ac above oj.uz—your main page will now show links from qoj.ac instead of oj.uz.


New Authentication Methods

You can now log in, register, or link your account using Google or Discord.


There's also tons of small changes and new settings that I've added not mentioned here.

Future Changes

Here are some features I plan to add next:
- Codebreaker sync (suggested by am_aadvik)
- On-hover problem statistics, similar to the old checklist (suggested by litvinas)
- A ranklist/users page based on total problems solved or other metrics (also suggested by am_aadvik)
- A more advanced /profile page that might include past virtual contests (suggested by me)

I’d also like to thank the two new contributors: mariza_CY and Nika533 for adding problems from IZHO and EJOI (along with contest data). You can do this too—check out the GitHub repository!


Any further suggestions are welcome and encouraged. Thanks for reading: now go register at https://checklist.spoi.org.in/!

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

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

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

Ever wanted to see how you’d score in CEOI 2024… as if you were actually there? With real medal cutoffs, percentiles, and your submissions auto-scored by subtasks: all just by entering your oj.uz username? Now you can.

What's inside

  • Precise tracking – mark problems as solved, partially solved, or assign an exact score out of 100.
  • Contest-based organization – problems grouped by Olympiad, year, and round.
  • Virtual contests – start any past contest with a live timer, track performance, and get an actual post-contest scoreboard.
  • Real historical data – medal type, rank, percentile — all based on the original contest results.
  • Auto-sync with oj.uz – past submissions automatically update your progress (for your entire checklist!) or in contest submissions for virtual contests.
  • Submission breakdown – see your score per subtask (in contests), not just the final number.
  • Dark mode & responsive UI – works on mobile, tablet, and desktop.

with even more settings for customization! Try it out yourself!

Also, you can share your checklist with other people (setting accessible via the dropdown menu on the main page), for example, here's mine: https://checklist.spoi.org.in/profile/avighna

Images

Dashboard after logging in Ongoing virtual contest

Dashboard after logging in, ongoing virtual contest

Virtual contest history Detailed performance breakdown

Virtual contest history, detailed performance breakdown

Contribute

The platform already covers a huge range of Olympiads, but there’s always room for improvement, especially for virtual contest data.

You can help by:

  • Adding contests that aren’t yet in the virtual contest library.
  • Adding problems from Olympiads you’d like to see supported.
  • Improving contest metadata (dates, locations, medal cutoffs, etc.).

Whether you’ve got a single contest to add or a whole archive, contributions are welcome. Check out the GitHub repository for instructions on how to get started.

A better OI Checklist

Some of you might remember the old OI Checklist from years ago. It was a great tool for its time, but it’s no longer updated, missing recent contests, closed source, and lacking a lot of features competitive programmers now expect.

This is the modern, actively maintained, open-source successor.

It already supports dozens of Olympiads— from IOI, APIO, CEOI, BOI, EGOI to USACO, NOI.sg, and even Google Kick Start— and it’s built to be easily extended.

Your next medal could start here: try the demo now, and if you find it helpful, please star the repo on GitHub.

Update: Please check the comments for new features added!

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

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

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

I've been working on this problem from JOISC 2020:

JOI20_building4 oj.uz link

Problem summary

You're given two arrays $$$a$$$ and $$$b$$$ of length $$$2n$$$. You have to pick exactly $$$n$$$ elements from each array to form a sequence of $$$2n$$$ total elements (alternating picks between the two arrays), such that the resulting sequence is non-decreasing. If possible, also print such a sequence using 'A' and 'B' to indicate from which array each element was chosen.

Dynamic programming

Here's a fairly natural DP approach:

Let $$$\text{dp}[i][j][k]$$$ be true if it's possible to build a valid sequence using the first $$$i$$$ elements, having picked exactly $$$j$$$ elements from array $$$a$$$, with the $$$i^\text{th}$$$ pick taken from:

  • $$$a$$$ if $$$k = 1$$$
  • $$$b$$$ if $$$k = 0$$$

This setup lets us ensure non-decreasing order, since we can directly compare the current value to the previous picked value (from either $$$a$$$ or $$$b$$$).

Let $$$arr[0] = b$$$, $$$arr[1] = a$$$. Then the transition becomes:

$$$\text{dp}[i][j][k]={\text{dp}[i-1][j-k][1]\,\text{and}\, a[i-1]\le \text{arr}[k][i]} \,\text{or}\,{\text{dp}[i-1][j-k][0]\,\text{and}\, b[i-1]\le \text{arr}[k][i]}$$$

Here's a code snippet for the transition:

dp[0][0][0] = dp[0][0][1] = true;
int *arr[] = {b, a};
for (int i = 1; i <= n << 1; ++i) {
  for (int j = 0; j <= n; ++j) {
    for (int k = 0; k < 2; ++k) {
      if (j - k >= 0) {
        dp[i][j][k] = (dp[i - 1][j - k][1] && a[i - 1] <= arr[k][i]) || (dp[i - 1][j - k][0] && b[i - 1] <= arr[k][i]);
      }
    }
  }
}

This solution is correct, but it's too slow ($$$O(n^2)$$$) to pass the full constraints.

Observation

This is where I’m stuck.

The editorial claims something interesting:

For fixed $$$i$$$ and $$$k$$$, the set of $$$j$$$ values for which $$$\text{dp}[i][j][k] == \text{true}$$$ always forms a contiguous interval.

That is, if $$$\text{dp}[i][j_1][k]$$$ and $$$\text{dp}[i][j_2][k]$$$ are both true, then for all $$$j$$$ in the range $$$(j_1, j_2)$$$, $$$dp[i][j][k]$$$ is also true.

This would allow us to compress the DP state $$$\text{dp}[i][*][k]$$$ into a simple $$$[l, r]$$$ range and optimize everything to $$$O(n)$$$.

And testing it seems to support the claim. I printed the $$$\text{dp}[2n][*][k]$$$ bitmasks for the sample inputs:

for (int i = 0; i <= n; ++i) {
  printf("%c", (char)dp[n << 1][i][0] + 0x30);
}
printf("\n");
for (int i = 0; i <= n; ++i) {
  printf("%c", (char)dp[n << 1][i][1] + 0x30);
}
printf("\n");

outputs

Sample 1:
0011
0000

Sample 2:
111
011

Sample 3:
000
000

Sample 4:
0011110
0001111

Looks like the $$$1$$$'s form a contiguous block every time.

My question

I have no clue how to prove this formally.

Why does the set of valid pick counts from a (i.e., $$$j$$$ values such that $$$\text{dp}[i][j][k] == \text{true}$$$) always form a contiguous interval?

Any ideas, intuition, or sketch of a proof would be very appreciated.

Thanks in advance!

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

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

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

If you’re into competitive programming, you’ve probably come across this fantastic blog. It introduces a super efficient, compact segment tree really shifted the way I thought about segment trees.

So today, while experimenting with this idea, I thought: "Why not extend it to support binary search?" And that’s exactly what I did. Here’s my attempt at it, and I thought it was worth sharing.

Quick recap: std::partition_point

If you’re familiar with binary search, you might know about std::partition_point. It’s a C++ function that returns the first false value in a range. C++20 also added std::ranges::partition_point (see one-line binary search), which makes binary search even more concise: basically, a one-liner.

Taking some inspiration from this (mainly for the name), here's how I adapted it for the segment tree:

Implementation

template <typename T> class SegmentTree {
public:
  int _n, n;
  T idt;
  std::vector<T> seg;
  std::function<T(T, T)> f;

  SegmentTree(int _n, std::function<T(T, T)> f = std::plus<T>(), T idt = T())
    : _n(_n), n(std::bit_ceil(uint32_t(_n))), idt(idt), f(f), seg(2 * n, idt) {}

  void set(int idx, T x) {
    for (seg[idx += n] = x, idx /= 2; idx > 0; idx /= 2) {
      seg[idx] = f(seg[2 * idx], seg[2 * idx + 1]);
    }
  }

  T query(int l, int r) {
    T ansL = idt, ansR = idt;
    for (l += n, r += n + 1; l < r; l /= 2, r /= 2) {
      if (l & 1) ansL = f(ansL, seg[l++]);
      if (r & 1) ansR = f(seg[--r], ansR);
    }
    return f(ansL, ansR);
  }

  int partition_point(int l, const std::function<bool(T)> &t) {
    T p = idt;
    for (l += n; t(f(p, seg[l])) and l & (l + 1); l /= 2) {
      if (l & 1) p = f(p, seg[l++]);
    }
    if (t(f(p, seg[l]))) {
      return _n;
    }
    while (l < n) {
      if (t(f(p, seg[l <<= 1]))) p = f(p, seg[l++]);
    }
    return l - n;
  }
};

The partition_point() function

This is where I added binary search. The partition_point() function finds the first index in the range $$$[l, n)$$$ where the predicate function t returns false by simulating a traversal of the segment tree.

Why share this?

I know there are other segment tree implementations out there, like AtCoder’s library, but I think this one is a bit cleaner and looks more elegant.

It’s not groundbreaking, but I thought I’d share it anyway. Maybe someone out there will find it useful.

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

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

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

You have $$$n+m$$$ points: ($$$x_i$$$, $$$y_i$$$, $$$\text{val}_i$$$). Each point satisfies $$$0 \le x_i \lt n$$$ and $$$0 \le y_i \lt m$$$. $$$\text{val}_i$$$ can be anything from $$$-10^9$$$ to $$$10^9$$$. You are at point $$$(0, 0)$$$ and you want to get to point $$$(n, m)$$$ maximizing your score. You get $$$\text{val}_i$$$ points if the path you go through contains point $$$i$$$ 'below' it. What is the maximum obtainable score?

For further clarification, by a point being 'below' a path, I mean: suppose your path was $$$(0, 0) \rightarrow (x_1, y_1) \rightarrow ... (x_k, y_k) \rightarrow (n, m)$$$. A point $$$(x', y')$$$ will be below this path if there exists a point in the path such that $$$x_i = x'$$$ and $$$y' \le y_i$$$.

Or refer to this image. The red points are below the path and the blue points are above it.

Naive dynamic programming on a grid is an option, of course, but this is at least $$$O(nm)$$$. In this problem, $$$n, m \le 1,000,000$$$ (however, there is a subtask with $$$n, m \le 200,000$$$). I'm looking for a solution with better time complexity, maybe something like $$$O((n + m) \log(n + m))$$$ using some kind of sweepline with segment trees. However, I can't figure out how to do this. Can somebody help?

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

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

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

Hello codeforces! Recently I was solving solar storm from NOI 2020's preliminary round on oj.uz. I made the following submission, which unfortunately WAed.

Spoiler

So I tried debugging it. I made several attempts, after which I decided to just look at the test data to see what I was failing. And this is the weirdest part, according to the official checker, my answer for 5-14 matches the optimal answer.

Multiple AC codes gave the exact same output that mine is givng.

(Note that I did also use the checker, and it also said that my answer was right.)

So then I thought of the next obvious issue: some kind of undefined behaviour? I checked this using valgrind (after commenting out my fast IO), and, unfortunately, there is none.

I also looked at the code of some AC submissions, and thier logic is almost identical to mine, so at this point, I really have no clue what I'm doing wrong, or what I can even do to debug this further (is there some way to see what my code was outputting when it received the wrong answer?) Please let me know if you do. Thank you!

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

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

Автор avighnakc, история, 2 года назад, По-английски

It's already April 18th- which means that INOI 2024 (the Indian National Olympiad of Informatics) is just three days away, happening on April 21st. As we approach the day, I (along with, I'm sure, other participants) am becoming increasingly nervous but also, undeniably, excited.

Good luck to everyone taking part! May the binary search be with you.

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

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

Автор avighnakc, 3 года назад, По-английски

Initial (Naïve) Approach

The most obvious initial approach would be to simply implement what the question is asking. Keep track of the number of killed monsters, keep finding the greatest health monster, subtracting $$$k$$$ from it and updating the value of the counter variable when necessary.

The code for this (which will be too slow) is as follows:

#include <bits/stdc++.h>
 
using namespace std;
 
#define ll long long
 
void solve() {
  ll n, damage;
  cin >> n >> damage;
  vector<ll> m(n);
  for (ll i = 0; i < n; ++i) {
    cin >> m[i];
  }
 
  ll killed = 0;
  while (killed != n) {
    ll mind = max_element(m.begin(), m.end()) - m.begin();
    if (m[mind] - damage <= 0) {
      cout << mind + 1 << " ";
      killed++;
    }
    m[mind] -= damage;
  }
  cout << "\n";
}
 
int main() {
  ll t;
  cin >> t;
  while (t--) {
    solve();
  }
}

Slightly Improved Naïve Approach

At this point, one may notice that instead of finding the largest monster in $$$O(n)$$$, one can instead use a std::priority_queue to do the same in $$$O(\log{n})$$$, but this is still too slow to pass.

There are various more optimizations that can be further made to the naïve approach, but I highly doubt anything will get it fast enough to be accepted. That being said, let's now move on to a better method.

Optimal Solution

Key Idea

If the initial health of the monsters is given as $$$[a_1, a_2, a_3, ..., nk, a_m, a_{m+1}, a_{m+2}, ...]$$$, and $$$a_x\mod k\neq0$$$, then the monster $$$nk$$$ will be the first one to die (i.e. get reduced to 0).

Here is the proof for this.

Note that if $$$nk \gt a_x$$$ at every stage, then this is trivially true (since we use the special attack on the largest element at every stage).

If not, then $$$nk \lt a_x$$$ (equality is impossible due to our indivisibility assumption). Since all monster healths are integers, $$$n \in Z^+$$$. If $$$n = 0$$$, then $$$nk$$$ has already been reduced to 0 (which is what we wanted to prove). Otherwise, $$$a_x \gt nk$$$ and thus $$$a_x \gt k$$$ ($$$n$$$ has now been proven to be a natural number) and $$$a_x - k \gt 0$$$. This tells us that at every single stage, the reduced value of any monster whose health is not divisible by $$$k$$$ can never go under $$$1$$$.

More specifically, after complete reduction, the health of any monster not divisible by $$$k$$$ will fall in the range of $$$[1, k)$$$ (if this were not true, then $$$nk$$$ would be reduced to $$$(n-1)k$$$ and then $$$k$$$ would be subtracted from $$$a_x$$$ again).

Thus, we can conclude that all monsters divisible by $$$k$$$ will be reduced first. Try to see why they will be equal to $$$k$$$ at the same time as well. Of course, since we choose the highest index first, we will go from left to right while eliminating them.

Building on this idea

We have previously concluded that all monsters divisible by $$$k$$$ will be killed first. In this process, all other monsters will also be reduced to the range $$$[1,k)$$$.

Then, since we choose monsters with the maximum health first, we'll kill the ones which are the greatest in the range $$$[1, k)$$$. These numbers have been reduced to this range as, previously, $$$k$$$ has been repeatedly subtracted from them. In other words (I'm sure some of you see what has to be done already): we kill the ones whose modulus with $$$k$$$ is the greatest first (here I'm referring to the original healths).

$$$a_x$$$ will be killed before $$$a_y$$$ if $$$m_x \mod k \gt m_y \mod k$$$.

I'd recommend that you try coding the solution yourself now. If you didn't completely understand any part, let your brain passively think about this idea for a few minutes and re-read this. With that being said, here's the solution code:

#include <bits/stdc++.h>
 
using namespace std;
 
#define ll long long
 
void solve() {
  ll n, k;
  cin >> n >> k;
  vector<pair<ll, ll>> m(n);
  for (ll i = 0; i < n; ++i) {
      cin >> m[i].first;
      m[i].first %= k;
      if (m[i].first == 0) {
        m[i].first = k;
      }
      m[i].second = -1 * i;
  }
 
  sort(m.begin(), m.end(), greater<pair<ll,ll>>());
  for (ll i = 0; i < n; ++i) {
    cout << -1*m[i].second+1<<" ";
  }
 
  cout<<"\n";
}
 
int main() {
  ll t;
  cin >> t;
  while (t--) {
    solve();
  }
}

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

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