Ashwanth.K's blog

By Ashwanth.K, history, 9 months ago, In English

Hi Codeforces, I would like to share some thoughts regarding a way of understanding SOS DP (This might already exist). Usually whenever I try to read about blogs on SOS DP, I find it a bit hard to comprehend the idea, why this simple 2 nested for loops works:

for(int i = 0;i < N; ++i) {
   for(int mask = 0; mask < (1<<N); ++mask) {
	if(mask & (1<<i)) {
		F[mask] += F[mask^(1<<i)];
        }
   } 
}

Lets try to solve a simpler problem:

Given a mask $$$M$$$ $$$(1 \leq M \lt 2^{20})$$$ , generate all its submasks (via recursion)

Solution:

  • We can iterate on bitwise (say from last bit to first bit)
  • if current bit b is set, we have 2 cases, either set or unset bit b in my submask.
  • if current bit b is unset, our submask also has this bit b as unset.

void gen_submask(int b , int curmask, int submask) { if(b < 0) { // basecase cout << submask << endl; return; } if(curmask&(1ll << b)) { gen_submask(b-1 , curmask , submask | (1ll << b)); // set gen_submask(b-1 , curmask , submask); // unset } else { gen_submask(b-1 , curmask , submask); // unset } } gen_submask(19 , mask , 0);

the above simple recursive function would print all the submasks for a given mask.

Optimisation 1: The second and third parameters (current mask , sub mask) can be combined into a single parameter.
Reason: Since we iterate on bitwise from last to first bit, say I am currently on bit b, the last bits (LSB .. b+1) of current mask is not required for my further computation, since the submask has been generated for those bits.

Say we are at bit b, We could maintain (LSB ... b+1) bits for submask and (b .. 0) bits contain information of input mask. By this way, we would always get information of bit which is under process, and also maintain generated submask in same variable.

Visual for better understanding of the optimisation:

Optimised code:

void gen_submask(int b , int curmask) {  // current bit , current mask
    if(b < 0) {
        cout << curmask << endl;  // basecase
        return;
    }

    if(curmask&(1ll << b)) {  // if bit is set, we have 2 cases
        gen_submask(b-1 , curmask);                 // set
        gen_submask(b-1 , curmask ^ (1ll << b));    // unset
    }
    else {
        gen_submask(b-1 , curmask);                 // unset
    }
}

gen_submask(19 , mask);

This code is simpler to understand, and this prints all submasks, for a given input mask, which serves our purpose.
Just memoize this recursive function, thats it, SOS DP.

Code

Conclusion:

  • Say any problem related to Sum of Subsets, Supersets, (or any variation of it) we could write a simple recursive function to generate all valid masks (as per the problem) and memoize it using DP.

Examples:

  • F(Mask) = Sum of all Masks, such that (odd bits are subset, even bits are superset)

[UPD] There is another interpretation of SOS DP, using higher dimensional prefix sums, Thanks to Misuki for the idea. Click Here

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

»
9 months ago, hide # |
 
Vote: I like it 0 Vote: I do not like it

Auto comment: topic has been updated by Ashwanth.K (previous revision, new revision, compare).

»
9 months ago, hide # |
 
Vote: I like it 0 Vote: I do not like it

Amazing!, my favorite type of blogs!

»
9 months ago, hide # |
 
Vote: I like it 0 Vote: I do not like it

Auto comment: topic has been updated by Ashwanth.K (previous revision, new revision, compare).

»
9 months ago, hide # |
 
Vote: I like it 0 Vote: I do not like it

Beginner friendly.

»
9 months ago, hide # |
Rev. 2  
Vote: I like it 0 Vote: I do not like it

https://codeforces.me/problemset/problem/1975/F a hard problem, but good to solve with this recursion idea

»
9 months ago, hide # |
Rev. 2  
Vote: I like it +75 Vote: I do not like it

Just want to mention there is an another easy way to understand SOS. That is, to think it as high dimension prefix sum.

For example, when the dimension is $$$2$$$, we can do something like

int a[n][m];
for(int i = 0; i < n; i++)
  for(int j = 1; j < m; j++)
    a[i][j] += a[i][j - 1];
for(int i = 1; i < n; i++)
  for(int j = 0; j < m; j++)
    a[i][j] += a[i - 1][j];

And it's not hard to see how to extend this to higher dimension: Just do prefix sum for each dimension. In the case of SOS, we are dealing with $$$n$$$ dimension array where each dimension have two possible index $$$\{0, 1\}$$$. So we would have something like

int a[2][2][2];

for(int i1 = 1; i1 < 2; i1++)
  for(int i2 = 0; i2 < 2; i2++)
    for(int i3 = 0; i3 < 2; i3++)
      a[i1][i2][i3] += a[i1 - 1][i2][i3];

for(int i1 = 0; i1 < 2; i1++)
  for(int i2 = 1; i2 < 2; i2++)
    for(int i3 = 0; i3 < 2; i3++)
      a[i1][i2][i3] += a[i1][i2 - 1][i3];

for(int i1 = 0; i1 < 2; i1++)
  for(int i2 = 0; i2 < 2; i2++)
    for(int i3 = 1; i3 < 2; i3++)
      a[i1][i2][i3] += a[i1][i2][i3 - 1];

Then store the array as $$$a[2^N]$$$ instead of $$$a[2][2][2][2][2]...$$$ and use some bitwise operation, you would get the code exactly the same as the first SOS code in your article.

»
9 months ago, hide # |
 
Vote: I like it 0 Vote: I do not like it

Auto comment: topic has been updated by Ashwanth.K (previous revision, new revision, compare).

»
9 months ago, hide # |
 
Vote: I like it 0 Vote: I do not like it

I was solving a problem where I needed an algorithm to find all submasks of all numbers in an array of size $$$ N \le 2*10^5 $$$. I didn't think of memoizing the backtracking. Thanks.

»
9 months ago, hide # |
 
Vote: I like it 0 Vote: I do not like it

Great blog!