QSET - Editorial

PROBLEM LINK:

Practice
Contest

Author: Lalit Kundu
Tester: Shiplu Hawlader
Editorialist: Lalit Kundu

DIFFICULTY:

MEDIUM

PRE-REQUISITES:

Segment Trees, Number Theory

PROBLEM:

Given a string of digits of length N<(105), do two kind of operations(total of M<(105):

  • Type 1: 1 X Y: Replace AX by Y.
  • Type 2: 2 C D: Print the number of sub-strings divisible by 3 of the string denoted by AC, AC+1 ... AD.

    Formally, you have to print the number of pairs (i,j) such that the string Ai, Ai+1 … Aj,
    (C ≤ i ≤ j ≤ D), when considered as a decimal number, is divisible by 3.

QUICK EXPLANATION:

Use segment trees. Store in node for each interval,

  • the answer for that interval
  • count1[], where count1[i] denotes number of prefixes of interval which modulo 3 give i.
  • count2[], where count2[i] denotes number of suffixes of interval which modulo 3 give i.
  • total, sum of interval modulo 3.

Perform merge operations in constant time.

EXPLANATION:

We basically need to find number of subarrays in range L to R who sum is divisible by 3.
Queries are for ranges, where we have to count subarrays, we can use segment tree because we can solve our problem if we can merge two intervals and find answer for the new interval in constant time.
We can use segment tree because we can take two different subarrays and merge them in constant time to find answer for the new large subarray.
In segment tree, each subarray’s information is recursively calculated from two smaller subarrays.
While querying, we can merge different(and disjoint) subarrays to get answer for range L to R.
So, we need to design the node of our segment tree. We store in our node, the answer for current subarray. Also, we will store two arrays count1[] and count2[] as defined above.
While merging two intervals(say node1 and node2), our new answer will be node1.answer + node2.answer, plus count of valid subarrays which start in node1 and end in node2.

image

This pseudo code will clear the things.

//merges node1 and node2 and stores in node3
merge(node1, node2, node3)
    //non intersecting subarrays of node1 and node2
    node3.ans = node1.ans + node2.ans
    for i = 0 to 2:
        for j = 0 to 2:
            //if adding suffix of node1 with modulo i
            //to prefix of node2 with modulo j
            //gives us a subarray divisible by 3
            if (i + j) % 3 == 0:
                //all pairs of valid indexes are valid subarrays
                node3.ans += node1.count2[i] * node2.count1[j]

Note now, we also need to build both array count1 and count2 for the new interval.
Let’s build count1 first:
Say there were count1[i] prefixes of node2 which when taken modulo with 3 gave i. Now, all such prefixes will give (i + node1.total) % 3, where node1.total denotes total sum of interval modulo 3. So, we can calculate new arrays. In similar way, we can calculate count2.

image

//building count1 and count2
for i=0 to 2:
    node3.count1[i] = node1.count1[i] + node2.count1[3-node1.total+i]
    node3.count2[i] = node2.count2[i] + node1.count2[3-node2.total+i]

So, complexity is: O(N log N) preprocessing and O(log N) per query.

ALTERNATIVE SOLUTION:

Suppose we keep in segment tree for each node, how many prefix sums in this interval are divisible by 0,1,2.
For a query [L,R], we just have to count number of prefix sums in interval [L,R] divisible by 0,1,2(let’s call them s1,s2,s3).
Our answer will be choose(s1,2)+choose(s2,2)+choose(s3,2).
Why? Because, suppose prefix_sum[i] % 3 = prefix_sum[j] % 3, then sum of substring [i+1, j] is divisible by 3.

For update query(mark A[x] = y), since we are keeping prefix sum modulo 3, for range [x, N], we increase/decrease each prefix sum by k(k<3). We can do this using lazy propagation.

SOLUTIONS:

Setter’s solution
Tester’s solution

11 Likes

This is my favorite in this contest :wink:

My solution was in O(sqrt(N) * N) - simplified idea is to cut the array in sqrt(N) segments, segment can be updated in sqrt(N) time and query is in 3 * sqrt(N) (I handle first and last separately - 2 * sqrt(N) and then I use precalculated values in all segments in between - last sqrt(N) ).

Who is greedy can see my solution, I’ll add detailed description a little later.

4 Likes

Hello! I am posting the link to my solution for QSET… I am getting TLE for the subtask 4… Can anyone please help me in optimizing my update_segment_tree function to avoid TLE??? An explanation added will be of very much help… Thank you! CodeChef: Practical coding for everyone

Although I used the same idea as given here, I got TLE on both subtask 3 and 4. Can anyone tell why?
http://www.codechef.com/viewsolution/5790168

Can someone please explain alternative solution for this problem, we have count of prefixes with remainder 0, 1 and 2 when divided by 3. Now how do we get number of substrings divisible by 3 using this information?
I got that given two prefixes with equal remainder, we will have a substring which is divisible by 3.
Hence if,

  • s1 = no. of prefixes giving remainder 1,
  • s2 = no. of prefixes giving remainder 2,
  • s3 = no. of prefixes giving remainder 0,

our answer should be s1*(s1-1)/2 + s2*(s2-1)/2 + s3*(s3-1)/2 ?
For ex- if the whole string is “133”,

  • s1 = 3 (1, 13, 133)
  • s2 = 0,
  • s3 = 0,

Our answer is 3*(2)/2 = 3.

Not what editorial suggest, i.e., s1*s1 = 9.

Am I missing or misunderstood something?

1 Like

This problem can be solved with TREAP , basicly whe storage the prefix sum mod 3 and we have 3 arrays that means T[ j ][ i ] = (AC[ i ] % 3 == j) where AC = prefix sum mod 3 and when we need to know the answer queries we need to know how many ones there has in T[ 0 ] , T[ 1 ] , T[ 2 ] in their respective intervals.
Finally for update queries we only swap sufix of T .

Sorry for my bad english.

Can anyone please explain the alternative solution in detail once more.
I cannot understand why answer will be choose(s1,2)+choose(s2,2)+choose(s3,2).

the links of Setter solution give xml error … please correct

The links to setters and tester’s solution is showing xml error! Please check the links!

please explain node3.count1[i] = node1.count1[i] + node2.count1[3-node1.total+i]

why this term:[3-node1.total+i] why not (i + node1.total) % 3

2 Likes

Editorial is pretty much clear… and explanatory… but can anyone plzz help me how to built a query function… means how can query for the range like 4 to 7 in segment tree when array size,n=8
only summing of node.ans is not enough so i am not able to manipulate how count1 and count2 is added… or it is to be added to find ans in the same way like in merge operation explained above…
@darkshadows
please explain it…Would be very much thankful if anybody can help me to understand…

count1[], where count1[i] denotes number of prefixes of interval which modulo 3 give i.

  • count2[], where count2[i] denotes number of suffixes of interval which modulo 3 give i.

what does this mean?

why are we doing this

 for i = 0 to 2:
        for j = 0 to 2:
            //if adding suffix of node1 with modulo i
            //to prefix of node2 with modulo j
            //gives us a subarray divisible by 3
            if (i + j) % 3 == 0:

in the pseudo code ?

what function choose(x,y) does?

how could i modify this to check if the substring between C and D inclusive is divisible by 3 ??
Or am i trying to explore a wrong approach for doing this ?

#include <stdio.h>

int main()
{
int rows, cols, i, j;

/*
 * Reads number of rows, columns to be printed
 */
printf("Enter number of rows: ");
scanf("%d", &rows);
printf("Enter number of columns: ");
scanf("%d", &cols);

for(i=1; i<=rows; i++)
{
    for(j=1; j<=cols; j++)
    {
        //Print 1 if its first or last row
        //Print 1 if its first or last column
        if(i==1 || i==rows || j==1 || j==cols)
        {
            printf("*");
        }
        else
        {
            printf(" ");
        }
    }

    printf("\n");
}

return 0;

}

why don’t you use CHelper + IntelliJ IDEA?

I’m an Eclipse guy. I tried IntelliJ IDEA and also NetBeans several times but I do not like those (maybe there is not rational reason for that)…

What I do not like on IDEs (not sure now if it is the same in IntelliJ) the most is, that there have to be just one main project/class and especially for contests I’m using it different way (I have same experience with all C++ IDEs, also Eclipse for C/C++ one :frowning: ) that’s my biggest problem in using other IDEs… In Eclipse I’m using run this class as Java/JUnit and I’m happy…

Ok. But, just so you know … you can achieve the same in IntelliJ IDEA.

1 Like

yes, you are right, that was probably a typo. fixed that!