BINREM - Editorial

PROBLEM LINK:

Practice
Contest: Division 1
Contest: Division 2
Contest: Division 3
Contest: Division 4

Author: rudra_1232
Tester: kingmessi
Editorialist: iceknight1093

DIFFICULTY:

Easy

PREREQUISITES:

None

PROBLEM:

You’re given a binary string S, and parameters K and X. The following operation can be performed:

  • Choose any subsequence of S that has at most X inversions, and the inversion count is a multiple of K.
    Then, delete this subsequence from S.

Find the minimum number of operations needed to empty S.

EXPLANATION:

The condition on inversions is quite hard to work with: keeping the count \leq X is one thing, but also ensuring the count is a multiple of K while doing this seems nearly impossible.

However, it can be observed that there is one case that’s quite simple: when the number of inversions is 0.
After all, 0 is certainly \leq X, and is also a multiple of K no matter what K is.

A subsequence having zero inversions essentially just means it’s sorted - or, specifically in the case of binary strings, has all its zeros before all its ones.
In particular, if a binary string contains only zeros or only ones, it certainly has zero inversions.

This immediately tells us that the answer is no larger than two: use one move to delete all the ones, and then another move to delete all the zeros.
So, all that needs to be done is to check if the answer is 1: if it isn’t, then the answer is 2.

For the answer to be 1, the only possible move is to delete the entire string at once.
This can easily be checked by computing the inversion count of the whole string and checking if it satisfies the conditions.

While there are general methods to compute the inversion count of an array in \mathcal{O}(N\log N) time, we can also utilize the fact that we’re working with a binary string to obtain a simple linear algorithm, because an inversion in a binary string is simply a subsequence that’s \texttt{"10"}.

  • Let \text{inversions} = 0 and \text{onect} = 0.
  • For each i from 1 to N,
    • If S_i = 1, increment \text{onect} by 1.
    • Otherwise, add \text{onect} to \text{inversions}.

TIME COMPLEXITY:

\mathcal{O}(N) per testcase.

CODE:

Editorialist's code (PyPy3)
for _ in range(int(input())):
    n, x, k = map(int, input().split())
    s = input()
    invs = ones = 0
    for c in s:
        if c == '1': ones += 1
        else: invs += ones
    if invs <= x and invs%k == 0: print(1)
    else: print(2)
Tester's code (C++)
//Har Har Mahadev
#include<bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp> // Common file
#include <ext/pb_ds/tree_policy.hpp>
#define ll long long
#define int long long
#define rep(i,a,b) for(int i=a;i<b;i++)
#define rrep(i,a,b) for(int i=a;i>=b;i--)
#define repin rep(i,0,n)
#define precise(i) cout<<fixed<<setprecision(i)
#define vi vector<int>
#define si set<int>
#define mii map<int,int>
#define take(a,n) for(int j=0;j<n;j++) cin>>a[j];
#define give(a,n) for(int j=0;j<n;j++) cout<<a[j]<<' ';
#define vpii vector<pair<int,int>>
#define db double
#define be(x) x.begin(),x.end()
#define pii pair<int,int>
#define pb push_back
#define pob pop_back
#define ff first
#define ss second
#define lb lower_bound
#define ub upper_bound
#define bpc(x) __builtin_popcountll(x) 
#define btz(x) __builtin_ctz(x)
using namespace std;

using namespace __gnu_pbds;

typedef tree<int, null_type, less<int>, rb_tree_tag,tree_order_statistics_node_update> ordered_set;
typedef tree<pair<int, int>, null_type,less<pair<int, int> >, rb_tree_tag,tree_order_statistics_node_update> ordered_multiset;

const long long INF=1e18;
const long long M=1e9+7;
const long long MM=998244353;
  
int power( int N, int M){
    int power = N, sum = 1;
    if(N == 0) sum = 0;
    while(M > 0){if((M & 1) == 1){sum *= power;}
    power = power * power;M = M >> 1;}
    return sum;
}

struct input_checker {
    string buffer;
    int pos;

    const string all = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    const string number = "0123456789";
    const string upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const string lower = "abcdefghijklmnopqrstuvwxyz";

    input_checker() {
        pos = 0;
        while (true) {
            int c = cin.get();
            if (c == -1) {
                break;
            }
            buffer.push_back((char) c);
        }
    }

    int nextDelimiter() {
        int now = pos;
        while (now < (int) buffer.size() && !isspace(buffer[now])) {
            now++;
        }
        return now;
    }

    string readOne() {
        assert(pos < (int) buffer.size());
        int nxt = nextDelimiter();
        string res;
        while (pos < nxt) {
            res += buffer[pos];
            pos++;
        }
        return res;
    }

    string readString(int minl, int maxl, const string &pattern = "") {
        assert(minl <= maxl);
        string res = readOne();
        assert(minl <= (int) res.size());
        assert((int) res.size() <= maxl);
        for (int i = 0; i < (int) res.size(); i++) {
            assert(pattern.empty() || pattern.find(res[i]) != string::npos);
        }
        return res;
    }

    int readInt(int minv, int maxv) {
        assert(minv <= maxv);
        int res = stoi(readOne());
        assert(minv <= res);
        assert(res <= maxv);
        return res;
    }

    long long readLong(long long minv, long long maxv) {
        assert(minv <= maxv);
        long long res = stoll(readOne());
        assert(minv <= res);
        assert(res <= maxv);
        return res;
    }

    auto readInts(int n, int minv, int maxv) {
        assert(n >= 0);
        vector<int> v(n);
        for (int i = 0; i < n; ++i) {
            v[i] = readInt(minv, maxv);
            if (i+1 < n) readSpace();
        }
        return v;
    }

    auto readLongs(int n, long long minv, long long maxv) {
        assert(n >= 0);
        vector<long long> v(n);
        for (int i = 0; i < n; ++i) {
            v[i] = readLong(minv, maxv);
            if (i+1 < n) readSpace();
        }
        return v;
    }

    void readSpace() {
        assert((int) buffer.size() > pos);
        assert(buffer[pos] == ' ');
        pos++;
    }

    void readEoln() {
        assert((int) buffer.size() > pos);
        assert(buffer[pos] == '\n');
        pos++;
    }

    void readEof() {
        assert((int) buffer.size() == pos);
    }
}inp;

int smn = 0;

void solve()
{
    int n,x,k;
    // cin >> n >> x >> k;
    
    n = inp.readInt(1,500'000);
    smn += n;
    inp.readSpace();
    x = inp.readInt(0,1000'000'000);
    inp.readSpace();
    k = inp.readInt(1,1000'000'000);
    inp.readEoln();
    string s;
    // cin >> s;
    s = inp.readString(n,n,"01");
    inp.readEoln();
    int inv = 0;
    int o = 0;
    for(auto &x : s){
        if(x == '1')o++;
        else inv += o;
    }
    if(inv <= x && ((inv%k) == 0))cout << 1 << "\n";
    else cout << 2 << "\n";
}

signed main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    #ifdef NCR
        init();
    #endif
    #ifdef SIEVE
        sieve();
    #endif
    int t;
    // cin >> t;
    t = inp.readInt(1,500'000);
    inp.readEoln();
    while(t--)
        solve();
    inp.readEof();
    assert(smn <= 500'000);
    return 0;
}
3 Likes
#include <bits/stdc++.h>
using namespace std;

void solve(){
    int n,x,k;cin>>n>>x>>k;
    string s;cin>>s;
    // we have to find no of inversions in a subseq 
    // we can pick a ss in which no of inversions are less than
    // x ans it should be divisible by k
    // if k > x we shuld definitly pick ss such that no of 
    // inversions are 0
    // at first we should have 
    // we can remove all elements in at max 2 operations 
    // pick all 0 and pick all ones 
    // in order to completly erase the sting we have to 
    // just check no of inversions 
    int invCnt = 0;
    int zeroCnt = 0;
    
    for(int i=n-1;i>=0;i--){
        if(s[i] == '0'){
            zeroCnt++;
        }
        else{
            invCnt+=zeroCnt;
        }
    }
    // 1 op means no of inversion should be 0 
    // or it satisfies the condition
    // cout<<invCnt<<endl;
    if(((invCnt%k) == 0) && (invCnt<=x)){
        cout<<1<<endl;
    }
    else{
  
        cout<<2<<endl;
    }
    
}

int main() {
	int t;cin>>t;
	while(t--){
	    solve();
	}

}

why is this giving wrong ans

2 Likes

bro you missed long long bro same like me

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

int main() {
    long long t; cin >> t;
    while (t--) {
        long long n, x, k; cin >> n >> x >> k;
        string s; cin >> s;
        long long invCnt = 0;
        long long zeroCnt = 0;

        for (long long i = n - 1; i >= 0; i--) {
            if (s[i] == '0') {
                zeroCnt++;
            } else {
                invCnt += zeroCnt;
            }
        }

        if (((invCnt % k) == 0) && (invCnt <= x)) {
            cout << 1 << endl;
        } else {
            cout << 2 << endl;
        }
    }
}

This code gives me the correct answer and passes all the test cases provided by “CodeChef.” However, surprisingly, for my test case, it gives the wrong answer.

Example:
Example given on the problem page:
6 100 2
111010

Here, the answer is 2. Yes, it is correct by their explanation:
111010 [removing 110 from index 1 to 3 (0-based indexing)]. The number of inversions in the subsequence 110 is 2, which is divisible by 2. By doing this, the string becomes 110 after removing the old 110 mentioned above.
Now, remove the subsequence 110 again to empty the string.
Note: “It can be proven that for the above string, there is no way to empty the string in fewer than 2 operations.” Yes, this is correct.

BUT WAIT, WAIT, WAIT!
Constraints:
1 ≤ T ≤ 5×10^5
1 ≤ N ≤ 5×10^5
1 ≤ K ≤ 10^9
1 ≤ X ≤ 10^9

Choose a substring such that the number of inversions in the subsequence lies in the range [0, X] and is divisible by K.

I am not creating a new test case, just modifying the above one. The value of X there is 100; I am just changing it to 1 (yes, I can choose 1 because of 1 ≤ X ≤ 10^9).
Now, the new example becomes:
6 1 2
111010

The X=1 says I can only choose a substring with its inversion in [0, 1], either NO inversion or a string with 1 inversion.
(All possible 1-inversion cases: 10, 010, 0010, 00010, 000010…)
But 1 can’t be divided by 2 (given: subsequence lies in the range [0, X] and is divisible by K).

So here, I have to choose strings having 0 inversion, which means it will only be 0 or 1.

Now going back to the example:
6 1 2
111010

As we can see, inversion 1 does not work because 1 % 2 != 0; the condition fails here.
So we must choose cases where inversion is 0, and the last letter or number of the string always has inversion 0. Also, 0 % 2 == 0 is correct.

Taking the above thought into consideration, the answer will be:
111010 -> 11101 (removing the last bit as inversion is 0) - 1st operation
11101 -> 1110 (removing the last bit as inversion is 0) - 2nd operation
1110 -> 111 (removing the last bit as inversion is 0) - 3rd operation
111 -> 11 (removing the last bit as inversion is 0) - 4th operation
11 -> 1 (removing the last bit as inversion is 0) - 5th operation
1 -> NULL (removing the last bit as inversion is 0) - 6th operation

And yes, for this example, “It can be proven that for the above string, there is no way to empty the string in fewer than 6 operations.”
However, using the above code and the editor’s code provided in the solution part, the answer we get is 2.

Another example:
9 2 2
111111110

It requires at most 7 operations to make the string empty, but the code gives the answer as 2.

Yes, I can create numerous such cases where I can prove the explanation and code wrong.

Like what!!! How am I supposed to solve this question?
Yes, I did change X from 100 to 1 and tried to solve this question yesterday. But today, when I saw the solution, I was like, “What?”
Either I am wrong, or the question is wrong.
And how some people are able to solve this question correctly, I don’t know either…

It is clearly mentioned in constraints that 0≤X≤1e9.

yes it is mentioned that 0≤X≤1e9. But can you explain me how
6 1 2
111010
gives answer as 2 because according to my calculation it is giving me 6.


For this also, 111010, if we remove a substring with 0 inversions, we first remove 111, and the string becomes 010.
Then we remove 01, and the string now becomes 0.
Lastly, we remove 0.

This method still takes 3 operations to empty the string if we select all with 0 inversions.


Is there any other way to do in two operations ?

I think I see where the confusion is coming from. You’re looking at this problem in terms of subarrays (continuous segments), but the question specifically asks for subsequences. Let me explain the difference:

  • A subarray must be continuous (like ‘010’ or ‘111’)
  • A subsequence just needs to maintain the relative order of elements, but they don’t need to be continuous

So in your example:
6 1 2
111010

If we number the indices as: 1 1 1 0 1 0
0 1 2 3 4 5We can solve it in 2 steps:

  1. First remove the subsequence 1111 by taking indices (0,1,2,4) - this maintains the relative order and has 0 inversions
  2. Then remove 00 by taking indices (3,5) - again maintains the order and has 0 inversions

Both these removals are valid because:

  • They maintain the relative order of elements (we just need to pick elements from left to right, they don’t need to be continuous)
  • Each has 0 inversions (which is in range [0,1] and divisible by 2)

This is why the answer is 2 operations, not more. The key insight is that subsequences can skip elements while maintaining order!

Note: This approach works for any binary string! We can always separate it into subsequences of all 1’s and all 0’s, solving it in at most 2 steps. If the string contains only 1’s or only 0’s, we can solve it in just 1 step.

1 Like

I didn’t know the concept of a subsequence. All this time, I was considering a substring, not a subsequence. Oops!

Thank you for correcting my mistake. :green_heart::trophy: