SEGTREES - Editorial

Problem Link : CodeChef: Practical coding for everyone

Difficulty : Hard

Problem : Queries to find the length of the shortest contiguous subarray in which all elements from 1 to K are present.

Explanation :
We will make a tree in such a way

findMinSubArr(L) :
Left : Left Half of the array L
Right : Right Half of the array L
A : findMinSubArr(Left)
B : findMinSubArr(Right)
C : minimum length of the subarray that contains all the numbers from 1 to K, made up of suffix of Left and prefix of R
return min( A, B, C)

Now, the main algo left to calculate the value of C in best time complexity.
First we will need to understand these things:

  1. As the value of K <= 50 so we can represent each number as a bitmask.
    (You can also make a hash array, but it will cost a lot of memory space)

  2. By bit masking we can easily check if a sequence_1 is a subset of sequence_2.
    Example : let seq_1 = [1, 3],
    We can represent every element of seq_1 by bit masking i.e. 1<<(element)
    seq_1 = [(1) << (1) , 1 << 3]
    Similarly seq_2 = [1,4,2,3] = [1 << 1, 1 << 4, 1 << 2, 1 << 3]
    Taking OR of every element of seq, we get
    OR1 (in binary) = 01010
    OR2 (in binary) = 11110
    if( OR1 & OR2 == OR1 ) then seq_1 is a subset of seq_2

  3. If the OR of the sequence == 2^K - 1, then we have found all the elements from 1 to K.

For each node of the tree, we will calculate the suffix of Left and prefix of Right in the form of bit masks. Now checking for which minimum length, the OR of the bitmask is 2^K -1.

Example :
Array = [1,2,3,1]
Left = [1,2] = {0110, 0100} // making the suffix array i.e each element stores the elements that come to the right of it. 0110 → 1 and 2 are visited

Right = [3, 1] = {0110, 0110} // making the prefix array i.e. each element of this array store the presence of the element to the left.

Now in process of making the tree is of time complexity : (K^2)Nlog(N):

For each suffix in left ( m ) we will check the the nearest prefix ( n ) to get the shortest length of the subarray, such that (m OR n) == (2^K - 1), which shows that all the elements from 1 to K are present in that subarray.

So the time complexity of building such a tree is (K^2)Nlog(N).

With some tricks this complexity can even be reduced to KNlog(N).

Now, have to process M queries

We will construct a Heap Tree where each node store the sets of bit masks for prefixes and suffixes of its interval and the length of the shortest subarray containing all the elements from 1 to K. Each query takes O(1) time.

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

typedef long long ll;

#define INF 1000000000
#define MAXN 262144
#define offset  131072


// described in the description
inline bool subset(ll mask1, ll mask2)
{
    return (mask1 & mask2) == mask1;
}

ll n, k, q;

class Heap
{
private:
    struct node
    {
        ll len;
        // make two arrays that store the suffix and prefix of the node
        // max array length is K = 50
        pair<ll, ll> pref[50];
        pair<ll, ll> suff[50];
        // stores the length of the minimum subarray containing all the elem
        // from 1 to K
        ll ans;

        // blank node
        node()
        {
            ans = INF;
            len = 0;
        }
        
        node(int t, int v)
        {
            len = 1;
            // bit masking the value
            // very important part
            pref[0] = suff[0] = pair<ll, ll>(1LL << v, t);
            ans = INF;
        }
    };

    node tree[MAXN];


    // merging of the left and right into the main tree
    void merge(node &t, node &l, node &r)
    {
        ll pref_len, suff_len;

        pref_len = 0;
        // the first part maintain the prefix array
        // maximum length of any segment can be 50 , as we are not pushing any value
        // if it is the subset of already made value
        for (int i = 0; i < l.len; ++i)
            t.pref[pref_len++] = l.pref[i];

        for (int i = 0; i < r.len; ++i){
            // if the right part is the subset of the prefix till now
            // then no need to inclde
            // by this way , the size of array in any node doex not exceed 50
            if (pref_len == 0 || !subset(r.pref[i].first, t.pref[pref_len - 1].first))
            {
                t.pref[pref_len] = r.pref[i];
                if (pref_len > 0)
                    t.pref[pref_len].first |= t.pref[pref_len - 1].first;
                ++pref_len;
            }
        }

        suff_len = 0;

        // same is the process of making the suffix array
        for (int i = 0; i < r.len; ++i)
            t.suff[suff_len++] = r.suff[i];
        for (int i = 0; i < l.len; ++i)
            if (suff_len == 0 || !subset(l.suff[i].first, t.suff[suff_len - 1].first))
            {
                t.suff[suff_len] = l.suff[i];
                if (suff_len > 0)
                    t.suff[suff_len].first |= t.suff[suff_len - 1].first;
                ++suff_len;
            }
        t.len = pref_len;
        t.ans = INF;
        ll pref_pos = 0;

        // now the part that is k^2 
        // which we can reduced to K
        // in this part we are checking 
        // the minimum length of the subarray which contain all the elem
        // from 1 to K
        for (int suff_pos = l.len - 1; suff_pos >= 0; --suff_pos)
        {
            while (pref_pos < r.len && (l.suff[suff_pos].first | r.pref[pref_pos].first) != (1LL << k) - 1)
                ++pref_pos;
            if (pref_pos < r.len)
            {   
                ll curr_mask = l.suff[suff_pos].first | r.pref[pref_pos].first;
                // if current mask  = 2^k - 1 , then it means
                // all the elements from 1 to K are founded
                if (curr_mask == (1LL << k) - 1)
                    t.ans = min(t.ans, r.pref[pref_pos].second - l.suff[suff_pos].second + 1);
            }
        }
        // calculating the minimum ans for the node
        t.ans = min(t.ans, min(l.ans, r.ans));
    }

public:

    // base constructor
    Heap() {}


    // update function
    // this is used for updating the left tree and right tree 
    // and then merging them to the main tree
    // movement is : bottom to top 
    void update(ll t, ll v)
    {   
        // as the movement is from bottom to top
        // so we make a offset that describe the position relative to the tree
        // let total nodes are 100
        // and length of the array is 10
        // then offset would be 90, because we are moving from bottom to top
        ll index = t +  offset;
        // making the node in the tree
        tree[index] = node(t, v);
        
        // merging the left and the right tree into the parent node
        for (index /= 2; index > 0; index /= 2){
            merge(tree[index], tree[2 * index], tree[2 * index + 1]);
        }
    }

    // the top of the tree will contain the length of the min sub array
    // just like min or max heaps
    ll query(void)
    {
        return tree[1].ans;
    }
};


// making a Heap tree
Heap T;

int main(void)
{
    cin >> n >> k >> q;
    for(int i = 0; i < n; i++){
        ll v;
        cin >> v;
        // updating or inserting the values in the tree
        T.update(i, v - 1);
    }

    while(q--)
    {
        ll type;
        cin >> type;
        if (type == 1)
        {
            ll x, v;
            cin >> x >> v;
            T.update(x - 1, v - 1);
        }
        else
        {
            ll ans = T.query();
            if( ans == INF){
                cout << -1 << endl;
            }else{
                cout << ans << endl;
            }
        }
    }

    return 0;
}
1 Like

"Now in process of merging the array is of time complexity : (K^2)Nlog(N) : "
What does the logn complexity correspond to?

Imo basic segTree can be built in O(n) time and here it should be O(n*k^2)

I think, with this complexity , TLE will be there.

This also. >?
please correct me if i m wrong .
@piyushbhutani

My Mistake, corrected. Thanks. Query is O(1)

How u will do update in O(1) ??

Where I have mentioned that, I am updating in O(1)

Update Query is O(K * log(n)) ig.