macaquedev's blog

By macaquedev, history, 46 hours ago, In English

Intro

Addition modulo p is simple, just add the two numbers, then modulo the result. Multiplication is also simple — multiply then take modulo. However, subtraction cannot be done in this way. This caught out quite a few people in today's Educational round due to the weak pretests on problem C, and I wanted to make a blog about it so that hopefully this doesn't happen again.

The pitfall

Take this: 306786744 submission for example. There were plenty that did exactly the same thing, but I just picked this one because it's relatively short. Specifically, let's look at the following line of code:

ans = (ans+one-onemain)%MOD;

You might think — what could possibly go wrong here? You're just adding and subtracting some numbers and taking modulo. Quite a lot as it turns out. The issue is that in C++, the modulo operator does not work well with negative numbers, and will just give you a negative remainder. This causes a problem because when I carefully (read: ask GPT to) construct a countertest, I can exploit this incorrect behaviour of the modulo operator, and cause your code to output the wrong answer. The way it's done is that I try to create a testcase where onemain is large, and ans+one is small (in this case, has just been wrapped round by the modulo operator). Now, we've got a negative number, and we take modulo, so now the calculation just outputs a negative number and you got hacked.

How to ensure this doesn't happen to you?

Two ways:

  1. Make sure to add MOD after subtracting two numbers, then take the modulo of the result. If we correct the code to ans = (ans+one-onemain+MOD)%MOD;, everything passes.

  2. Just use a modular arithmetic template. It comes up in so many problems, so I think it's a waste of time writing all this MOD stuff out every time, and it could save you a lot of WA, or if you're REALLY unlucky like today, FST.

Hope this helps someone!

  • Vote: I like it
  • +25
  • Vote: I do not like it

»
44 hours ago, # |
Rev. 2   Vote: I like it 0 Vote: I do not like it

Such an informative blog I wonder why the downvotes?

  • »
    »
    44 hours ago, # ^ |
      Vote: I like it +18 Vote: I do not like it

    downvoted by the people who got hacked I guess?

»
44 hours ago, # |
  Vote: I like it +21 Vote: I do not like it

bro thinks he's djm03178

»
43 hours ago, # |
  Vote: I like it 0 Vote: I do not like it

or maybe find a MODINT template online?

»
42 hours ago, # |
  Vote: I like it +9 Vote: I do not like it

As a python user, this hack doesn't work on us fortunately.

»
42 hours ago, # |
  Vote: I like it +1 Vote: I do not like it
a template of struct modint
  • »
    »
    41 hour(s) ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    do you have the template in java plz share it if you have

    • »
      »
      »
      41 hour(s) ago, # ^ |
        Vote: I like it 0 Vote: I do not like it

      i dont use java :( but it's just simple class and overloading operator, u can make it by urself :)

»
38 hours ago, # |
Rev. 3   Vote: I like it 0 Vote: I do not like it

This whole problem only exists due to the weak pretests in C today, which I already ranted about in the blog post itself so I will go more into depth about this issue here instead

but to elaborate in detail you can construct something such that

"one" in the case of the example becomes a number slightly greater than or equal to M

and making sure there are more 1s before than this number

some examples i used, you could do this by getting binary representation and whenever seeing a 1 in it adding 1 2 (or 3 2 depending on the sol you're hacking), and when seeing a 0 add a 2

3
37
1 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 2 3 2 3 2 2 3 2 3 2 3
37
1 2 1 2 1 2 2 1 2 1 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 3
38
1 2 1 2 1 2 2 1 2 1 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 3
  • »
    »
    37 hours ago, # ^ |
    Rev. 2   Vote: I like it 0 Vote: I do not like it

    It's pretty hard to expect a test to randomly hack it when a more natural solution without subtraction exists, and they probably didn't notice it during contest preparation.

  • »
    »
    36 hours ago, # ^ |
    Rev. 5   Vote: I like it 0 Vote: I do not like it

    Even just making ones=MOD should fail

    1 1 1 2 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 3

»
37 hours ago, # |
  Vote: I like it -26 Vote: I do not like it

ans = (ans+one-onemain+MOD)%MOD; it would still be wrong.

right: ans = ((ans+one-onemain)%MOD+MOD)%MOD.

  • »
    »
    36 hours ago, # ^ |
      Vote: I like it 0 Vote: I do not like it

    ans = (ans+one-onemain+MOD)%MOD; is enough when onemain is guaranteed to be less than MOD, which is the case for this code.

    • »
      »
      »
      36 hours ago, # ^ |
        Vote: I like it -11 Vote: I do not like it

      Yes, but in order not to take any risk, it's better to do what I said.

      • »
        »
        »
        »
        36 hours ago, # ^ |
          Vote: I like it +9 Vote: I do not like it

        A more general advice would be to keep every value used for calculation within $$$[0,MOD)$$$. This way, adding a single MOD after each subtraction always works, and performing modulo multiple times can be very slow sometimes.