Tuesday, March 20, 2012

SRM 538 DIV1 and DIV2

Today's SRM, at least DIV 1, seemed to be heavily focused on math.
I did okay - I passed 250 and failed 500.
After the system test, I found out why I failed 500, and solved it in a practice room.


DIV1

250 - EvenRoute

Approach
I will update this shortly.

Source Code

1:  class EvenRoute {  
2:  public:  
3:       string isItPossible(vector <int>, vector <int>, int);  
4:  };  
5:  string EvenRoute::isItPossible(vector <int> x, vector <int> y, int pa) {  
6:       bool hasEven = false, hasOdd = false;  
7:    for(int i=0; i < x.size(); i++)  
8:      if( (abs(x[i]) + abs(y[i])) % 2 == 0) hasEven = true;  
9:      else hasOdd = true;  
10:    if( (pa == 0 && hasEven) || (pa == 1 && hasOdd) ) return "CAN";  
11:    return "CANNOT";  
12:  }  

500 - TurtleSpy

Approach
It is a simple math problem that needs a bit of observation.

Lemma 1. It is always optimal to use forward vectors in the same direction.
Proof.
We want to maximize the euclidian distance (E.D).
Let sumF  = sum of all forward vectors.
If we use forward vectors as a straight line, we get sumF for the E.D.
However, if we change our direction while connecting forward vectors, we will make the total shape of vectors slight bent. And the E.D for the bent vector will be smaller than the straight line.

Now, we established Lemma 1, and note that the same logic applies to backward vectors.
We can now make one more observation.

Lemma 2. It is optimal to turn as close to 180 degrees possible after finishing using forwarding vectors.
Proof.
backward vectors are essentially (Turn 180 degrees + forward vectors), or negative forward vectors.
Let sumB = sum of all backward vectors.
If we turn 180 degrees, the final E.D is sumF  + sumB, and the more we turn away from 180 degrees,
our E.D. will get smaller and smaller.

Let's define
OPT = optimal degree possible we can gain using given "degree" vectors. (left X and right X).
F = sum of all forward vectors.
B = sum of all backward vectors.

Then our answer is
sqrt( (F  - B * cos(OPT))^2 + (B * sin(OPT))^2 )
= sqrt ( F^2 - 2*F*B*cos(OPT) + (B*cos(OPT))^2 + (B*sin(OPT))^2
= sqrt( F^2 + B^2*(cos^2(OPT) + sin^2(OPT)) - 2*F*B*cos(OPT))
= sqrt(F^2 + B^2 - 2*F*B*cos(OPT))

So all we have to do is find OPT.
Note that there are 360 possible degrees, and at most 50 degree vectors.
We can try them all, and it will take only 360*50 operations at most.

Source Code
1:  double TurtleSpy::maxDistance(vector <string> commands) {  
2:       int F = 0;  
3:    int B = 0;  
4:    double PI = acos(-1);  
5:    vector<bool> deg(361, false);  
6:    deg[0] = true;  
7:    for(int i=0; i < commands.size(); i++)  
8:    {  
9:      stringstream ss(commands[i]);  
10:      string cmd;  
11:      int X;  
12:      ss>>cmd>>X;  
13:        
14:      if(cmd[0] == 'f') F+=X;  
15:      else if(cmd[0] == 'b') B+=X;  
16:      else  
17:      {  
18:        vector<bool> temp = deg;  
19:        if(cmd[0] == 'r')  
20:        {  
21:          for(int i=0; i < 360; i++)  
22:            if(temp[i])  
23:              deg[ (i+X) % 360 ] = true;  
24:        }  
25:        else //cmd[0] == 'L'  
26:        {  
27:          for(int i=0; i < 360; i++)  
28:            if(temp[i])  
29:              deg[ (i-X + 360) % 360 ] = true;  
30:        }  
31:      }  
32:    }  
33:      
34:    double opt = 0.0;  
35:    int away = 0;  
36:    while(away <= 180 && opt == 0.0)  
37:    {  
38:      if(deg[180 - away] || deg[180 + away])  
39:        opt = (180.0 - away);  
40:      away++;  
41:    }  
42:    double df = double(F);  
43:    double db = double(B);  
44:    return sqrt(df*df - 2.0*df*db*cos(opt * PI / 180.0) + db*db);  
45:  }  
46:    


DIV2


250 - LeftOrRight
Source Code
1:  class LeftOrRight {  
2:  public:  
3:       int maxDistance(string);  
4:  };  
5:    
6:  int LeftOrRight::maxDistance(string program) {  
7:         
8:    //? = left  
9:    int left = 0, right = 0;  
10:    int ans = 0;  
11:    for(int i=0; i < program.size(); i++)  
12:    {  
13:      if(program[i] == 'L')  
14:      {  
15:        left++;  
16:        right--;  
17:      }  
18:      else if(program[i] == 'R')  
19:      {  
20:        left--;  
21:        right++;  
22:      }  
23:      else  
24:      {  
25:        left++;  
26:        right++;  
27:      }  
28:      ans = max(ans, max(left, right));  
29:    }  
30:    return ans;  
31:  }  
32:    




500 - EvenRoute  => same as div 1 250


1000 - skewedPerspectives
Source Code
1:  class SkewedPerspectives {  
2:  public:  
3:       vector <string> areTheyPossible(vector <int>, int, int, vector <string>);  
4:  };  
5:    
6:  vector <string> SkewedPerspectives::areTheyPossible(vector <int> cubes, int B, int w, vector <string> views) {  
7:         
8:    vector<string> ans;  
9:    for(int v=0; v < views.size(); v++)  
10:    {  
11:      //basic test  
12:      if( (views[v].size() == 1 && views[v][0] == 'b') || (views[v].size() > 1 && views[v][0] == 'b' && views[v][1] != 'b') )  
13:      {  
14:        ans.push_back("invalid");  
15:        continue;  
16:      }  
17:        
18:      string curView = 'x' + views[v];  
19:      vector<int> curCubes = cubes;  
20:      vector<int> extra;  
21:      bool valid = true;  
22:      int curB = B;  
23:        
24:      int h = curView.size() - 1;  
25:        
26:        
27:      while(h >= 1)  
28:      {  
29:        if(curView[h] != 'b')  
30:        {  
31:          curCubes[ curView[h] - '0' ]--;  
32:          h--;  
33:        }  
34:        else  
35:        {  
36:          int low = h;  
37:          while(curView[low] == 'b')  
38:            low--;  
39:          int oldH = h;  
40:          h = low;  
41:            
42:          if( (oldH-low) % 2 == 0)  
43:          {  
44:            curB -= (oldH-low)/2;  
45:          }  
46:          else  
47:          {  
48:            curB -= (oldH - low + 1) / 2;  
49:              
50:            if(low == 0) extra.push_back(1);  
51:            else extra.push_back(low-1);  
52:          }  
53:        }   
54:      }  
55:        
56:      int ones = 0;  
57:      for(int i=0; i < curCubes.size(); i++)  
58:      {  
59:        if(curCubes[i] < 0)  
60:        {  
61:          valid = false;  
62:          break;  
63:        }  
64:        ones += curCubes[i];  
65:      }  
66:        
67:      if(!valid || curB < 0 || (extra.size() + 1) > w)  
68:      {  
69:        ans.push_back("invalid");  
70:        continue;  
71:      }  
72:        
73:      for(int i=0; i < extra.size(); i++)  
74:      {  
75:        while( curB > 0 && (extra[i] - 2 >= 0) )  
76:        {  
77:          curB--;  
78:          extra[i]-=2;  
79:            
80:        }  
81:          
82:        while( ones > 0 && (extra[i] - 1 >= 0) )  
83:        {  
84:          ones--;  
85:          extra[i]--;  
86:        }  
87:          
88:        if(extra[i] > 0)  
89:        {  
90:          valid = false;  
91:          ans.push_back("invalid");  
92:          break;  
93:        }  
94:      }  
95:        
96:      if(valid)  
97:        ans.push_back("valid");  
98:    }  
99:    return ans;  
100:  }  


Saturday, March 17, 2012

TopCoder SRM 537 DIV1 and DIV2

DIV 1

250 - KingXNewCurrency

Approach
It's a math problem.
A = X * a + Y * b
B = X * c + Y * d
where  a,b,c,d are non-negative integers.
In other words, A and B are integers that can be made by adding multiples of X and Y.

Then we can rewrite above formula as follow
A = X * a + Y * b
Y*b = A - X * a

B  = X * c + Y * d
Y * d = B -  X * c

Let's define
As = positive values of (A - X*a) where 0 <= a
Bs = positive values of (B - Y*c) where 0 <= c
Then we are looking for integers Y that satisfy
As[i] % Y == 0 and Bs[j] % Y == 0

Constraint (1 <= A,B <= 200) is small enough to try all possible values of Y

Source code

1:  class KingXNewCurrency {  
2:  public:  
3:       int howMany(int, int, int);  
4:  };  
5:    
6:    
7:  int KingXNewCurrency::howMany(int A, int B, int X) {  
8:         
9:    if(A % X == 0 && B % X == 0)  
10:      return -1;  
11:      
12:    int ans = 0;  
13:    vector<int> As;  
14:    vector<int> Bs;  
15:      
16:    int k = 0;  
17:    while( A - X*k >= 0)  
18:      As.push_back(A - X * (k++));  
19:      
20:    k = 0;  
21:    while(B - X*k >= 0)  
22:      Bs.push_back(B - X * (k++));  
23:      
24:    for(int Y=1; Y <= max(A,B); Y++)  
25:    {  
26:      if(X == Y) continue;  
27:      bool found = false;  
28:      for(int i=0; !found && i < As.size(); i++)  
29:        for(int j=0; !found && j < Bs.size(); j++)  
30:          if(As[i] % Y == 0 && Bs[j] % Y == 0)  
31:          {  
32:            ans++;  
33:            found = true;  
34:          }  
35:    }  
36:      
37:    return ans;  
38:  }  
39:    

Note:
I actually failed system test for this problem because I wrote
As[i] % Y == 0 && Bs[i] % Y == 0  at line 30 during contest.
Stupid mistake. I know.




500 - KingXMagicSpells

Note : I couldn't solve this problem on my own, so I searched the internet, and found a blog explaining this problem very well. It is written by vexorian, and here's the link to his blog : vexorian.blogspot.com 

I made minor modifications to variables names and code structure to help me better understand his algorithm, but I didn't make any major changes to his algorithm. So I am basically paraphrasing vexorian's algorithm here.
Feel free to go to vexorian's blog, and only come back here if you are still confused after reading vexorian's blog and need MORE DETAILS.

Approach

Each day, there are two possible operations
1) XOR  operation caused by SpellOne
2) SWAP operation caused by SpellTwo

In other words, there are 2 possible outcomes every day.
There are K days, and 1 <= K <= 50
So there can be 2^50 = (2^10)^5 = (10^3)^5 = 10^15  possible outcomes at most.
10^15 tells us that trying all possible outcomes will undoubtedly go over time limit.

Note that the result of each day (number of ducks at room 0 on the current day ) depends on the previous day.  It is a counting problem with a recurrence, which means it is a dp-problem.

The tricky part of this problem is XOR operation. My initial reaction when I first saw this problem was to define dp as
dp[i][j] = expected number of ducks at room i after j operations.
And recurrence is
dp[i][j] = 0.5 * (spellOne[i] ^ dp[i][j-1]) + 0.5 * (dp[ prev[i] ][j-1])
where prev[i] = previous position of room i before a SWAP operation.


However, I failed miserably because XOR operation can be performed only between integers. Since dp[i][j] stores a double, XOR operation cannot be used in a recurrence.

The solution to the problem caused by XOR is to redefine dp.
dp[bit][bitPos][roomPos][ops]
= probability of the number of ducks in "roomPos" room having "bitPos"th bit set to "bit" after "ops" number of operations.

Recurrence
dp[bit][bitPos][roomPos][ops]
= 0.5 * dp[( bit ^ (spellOne[roomPos]>>bitPos) & 1)][bitPos][roomPos][ops-1]
+ 0.5 * dp[bit][bitPos][prev[roomPos][ops-1]

Base case
if(ops == 0)
{
      if( (ducks[roomPos] >> bitPos) & 1) == bit ) return 1.0;
      else return 0.0;
}

Our answer is
1 * probability of 0th bit of duck[0] being 1 after K operations
+ 2 * probability of 1th bit of duck[0] being 1 after K operations
+ 4 * probability of 1th bit of duck[0] being 1 after K operations
+ 8 * probability of 1th bit of duck[0] being 1 after K operations
...
==> sum of (1<<bitPos * dp[1][bitPos][0][K] where 0 <= bitPos <= 30
The constraint for bitPos came from here.
0 <= ducks[i] <= 10^9
10^9 = (10^3)^3 = (2^10)^3 = 2^30
So the leftmost bit we have to check is at 30th position.


Source Code


1:  class KingXMagicSpells {  
2:  public:  
3:       double expectedNumber(vector <int>, vector <int>, vector <int>, int);  
4:  };  
5:    
6:  const int maxN = 51;  
7:  const int maxDig = 31;  
8:  vector<int> s1;  
9:  vector<int> s2;  
10:  vector<int> dk;  
11:    
12:  int prev[maxN]; //prev[i] = previous position of room i before a swap  
13:  double dp[2][maxDig][maxN][maxN];  
14:  bool chk[2][maxDig][maxN][maxN];  
15:    
16:  double rec(int,int,int,int);  
17:    
18:  double KingXMagicSpells::expectedNumber(vector <int> ducks, vector <int> spellOne, vector <int> spellTwo, int K) {  
19:    s1 = spellOne; //XOR operation  
20:    s2 = spellTwo; //swap operation  
21:    dk = ducks;  
22:    int N = s1.size();  
23:      
24:    for(int i=0; i < N; i++)  
25:      prev[ s2[i] ] = i;  
26:      
27:    memset(chk, false, sizeof(chk));  
28:      
29:    double ans = 0.0;  
30:    for(int bitPos=0; bitPos < maxDig; bitPos++)  
31:      ans += (1<<bitPos) * rec(1, bitPos, 0, K);  
32:      
33:    return ans;  
34:  }  
35:    
36:  double rec(int bit, int bitPos, int roomPos, int ops)  
37:  {  
38:    double& res = dp[bit][bitPos][roomPos][ops];  
39:      
40:    if(chk[bit][bitPos][roomPos][ops])  
41:      return res;  
42:      
43:    res = 0.0;  
44:    chk[bit][bitPos][roomPos][ops] = true;  
45:      
46:    if(ops == 0) //end of operation  
47:    {  
48:      if( ((dk[roomPos]>>bitPos) & 1) == bit ) res = 1.0;  
49:      return res;  
50:    }  
51:      
52:    //XOR : spellOne  
53:    res += 0.5 * rec( bit ^ ((s1[roomPos]>>bitPos) & 1), bitPos, roomPos, ops - 1);  
54:      
55:    //permutation : spellTwo  
56:    res += 0.5 * rec(bit, bitPos, prev[roomPos], ops - 1);  
57:      
58:    return res;  
59:  }  

DIV 2


250 - KingXNewBaby

Approach
We do exactly what the problem asks us.
1. Check if the name is of length 8.
2. Check if the name contains exactly two vowels.
3. Check if the two vowels are the same.

Source Code

1:    
2:  class KingXNewBaby {  
3:  public:  
4:       string isValid(string);  
5:  };  
6:    
7:  bool isVowel(char);  
8:    
9:  string KingXNewBaby::isValid(string name) {  
10:       int N = name.size();  
11:      
12:    if(N != 8) return "NO";  
13:      
14:    char vowels[8];  
15:    int sz = 0;  
16:    for(int i=0; i < N; i++)  
17:      if(isVowel(name[i]))  
18:        vowels[sz++] = name[i];  
19:      
20:    if( sz == 2 && vowels[0] == vowels[1])  
21:      return "YES";  
22:    return "NO";  
23:  }  
24:    
25:  bool isVowel(char ch)  
26:  {  
27:    string str = "aeiou";  
28:    for(int i=0; i < 5; i++)  
29:      if(ch == str[i]) return true;  
30:    return false;  
31:  }  
32:    


500 - KingXNewCurrency
It is the same problem as DIV 1 250. Please go to DIV1 - 250 at the beginning of this blog.



1000 - PrinceXToastbook

I will shortly post a full approach for this problem.
I can tell you right now that this requires
1) permutation
2) combination

In other words, this is a math problem, and it can be solved in O(n) time.

Source Code

1:  class PrinceXToastbook {  
2:  public:  
3:       double eat(vector <int>);  
4:  };  
5:    
6:  vector<int> pre;  
7:  vector<int> topSort(50, -1);  
8:    
9:  ll pascal[51][51];  
10:  int N;  
11:    
12:  /*  
13:   numPrev(i,j) = number of books to be read (prerequisite books)  
14:   to understand book i.  
15:   j is a bitmask representing books read so far.  
16:   */  
17:  int numPrev(int, ll);   
18:  void combiPascal(); //pascal's table for combination  
19:    
20:  double PrinceXToastbook::eat(vector <int> prerequisite) {  
21:    pre = prerequisite;  
22:       N = pre.size();  
23:      
24:    combiPascal();  
25:      
26:    double permu[51]; //permutation  
27:    permu[0] = permu[1] = 1.0;  
28:    for(double i = 2.0; i <= 50; i+=1.0)  
29:      permu[int(i)] = i * permu[int(i-1)];  
30:      
31:    double ans = 0;  
32:      
33:    REP(i,0,N)  
34:    {  
35:      topSort[i] = numPrev(i, 0ll);  
36:      if(topSort[i] >= N) continue; //cycle exists  
37:      ans += double(pascal[N][topSort[i]+1]) * permu[N-topSort[i]-1];  
38:    }  
39:      
40:    return ans/permu[N];  
41:  }  
42:    
43:  int numPrev(int idx, ll visited)  
44:  {  
45:    int& res = topSort[idx];  
46:    if(res != -1) return res;  
47:      
48:    if(pre[idx] == -1)  
49:      return 0;  
50:      
51:    if( ((visited>>idx) & 1) == 1) //cycle found  
52:      return N;  
53:      
54:    visited += (1ll<<idx);  
55:    res = 1 + numPrev(pre[idx], visited);  
56:      
57:    return res;  
58:  }  
59:    
60:  //makes pascal table. It can be used to get a combination.  
61:  //For example, pascal[10][5] is the same as number of ways to pick 5 items out of 10 items  
62:  void combiPascal()  
63:  {  
64:       memset(pascal, 0, sizeof(pascal));  
65:         
66:       pascal[0][0] = 1;  
67:       for(int i=0; i <=50; i++)  
68:            pascal[i][0] = 1;  
69:         
70:       for(int i=1; i <= 50; i++)  
71:            for(int j=1; j <= 50; j++)  
72:                 pascal[i][j] = pascal[i-1][j-1] + pascal[i-1][j];  
73:  }  
74:    

Sunday, March 11, 2012

Codeforces VK Cup Qualification Round 2 Editorial

I participated in Codeforces VK Cup Qualification Round 2, and passed (advanced to Round 1).
I solved 4 problems, and got "time limit exceed" on problem E.
After the contest, I looked at codes of other contestants who passed problem E, and realized they used the same algorithm that I used.
It was how I implemented my algorithm that made me fail problem E.

People who passed used (in C++)
1. scanf to read input
2. printf to write output
3. Used arrays to hold data

I used (also in C++)
1. cin to read input
2. cout to write read output
3. vector to hold data.

I googled, and found out that
1. cin and cout are slower than scanf and printf
Because cin and cout use streams (note that we have to include #<iostream> to use them. iostream = input/output stream), they are slower than scanf and printf, which are plain standard input/output (#include <cstdio>).
In real life, safety is important and therefore streams are preferred,
but during algorithm contest where speed is crucial, scanf and printf are better choices.

2. vector can be slower than array
Certain methods of vector in STL are AMORTIZED O(1), meaning they are not O(1) in certain cases.
For example, push_back method is O(n) when the current vector hits its capacity limit and resizing should happen.
So if you know the maximum size of input, it is better to initialize your array to the maximum length instead of making an empty vector and dynamically change its length.

Maybe these facts are old news, but me being a newbie and still fairly new to algorithm contests, these were surprising to me. After experimenting with my code, I found out that using arrays instead of vectors could have made my code pass problem E.

I linked each problem title to its problem statement (e.g. clicking Problem E - Zebra Tower will lead you to its problem statement).


Problem E - Zebra Tower

Approach

We want to build maximum-height tower that meets following criteria.
1. Towers are made by stacking cubes, and only two colors of cubes are allowed in a tower. "
2. Colors of cube should be alternating.

The problem stated that "It is guaranteed that there are at least two cubes of different colors" , which means there is always an answer.
Before we try to figure out a way to find an answer, let's think about how an answer will look like first.

Terms & Definitions
Opt = an optimal answer, or a tower with the maximum-height (as the problem statement said, it is possible that there is more than one optimal answer).

bestA = collection of cubes of the same color that make Opt.
A = color of cubes in bestA

bestB = collection of cubes of the same color (, and not the same color as bestA) that make Opt.
B = color of cubes in bestB

Since colors of cubes should be alternating, 3 possible scenarios
1. bestA.size() == bestB.size()
2. bestA.size() == bestB.size() - 1
3. bestA.size() == bestB.size() + 1

In all three cases, the absolute difference of size of bestA and bestB do not exceed 1.

However,  note that it is possible that there can be more cubes with color B, or color A that are not part of Opt.
For example, let's say there are 5 cubes of color A and 9 cubes of color B.Then we can only use 5 cubes of color A and 6 cubes of color B.
Let's call
A_all = number of all cubes of color A
B_all = number of all cubes of color B

For our convenience, let's assume that A_all <= B_all
This assumption is harmlessly because in case b_all > A_all, we can just swap color A and B to fit our observations.

Lemma 1. A_all = bestA  .

If A_all != A.size(), it means there is Extra( = A_all - bestA.size())  number of cubes of color A that are not part of Opt.  Since A_all <= B_all, it is possible to add those Extra number of A cubes. Because height of each cube is positive, this means it is possible to build a tower higher than Opt. It is a contradiction.
In other words, at least one of collection of cubes can use all of its cubes to build Opt.


Lemma 2. The E (>= 0) number of cubes of color B that are not part of B are the smallest cubes in B_all.

Assume the contrary :
B_ex = a cube with color B that is not part of Opt.
B_in = a cube with color B that is part of Opt.
And height of B_ex  > height of B_in.
Let's swap B_ex and B_in : Take B_in out of Opt and put B_ex into Opt.
Than the height of Opt increase by the difference of height between B_ex and B_in, which means a tower higher than Opt exists.
This is a contradiction.


Algorithm
We will take advantage of observations we made about an optimal answer.
We will first group cubes by its color.
For each color group, we will assume that it is color group B in our assumption.
So for all possible height for the current group, we will build a tower by using all of cubes of one of other color groups.


Note.
I made some optimizations, like saving info on which color group can produce maximum height when you can use only "i" number of cubes.
I explained them in detail in my source code.


Source Code

1:  #include <cstring>  
2:  #include <iostream>  
3:  #include <algorithm>  
4:    
5:  #define maxN 100010  
6:  using namespace std;  
7:    
8:  //Represents a cube  
9:  struct Cube  
10:  {  
11:    int color,height,idx;  
12:  };  
13:    
14:  //Cubes with same color  
15:  struct CubeSet  
16:  {  
17:    int len;  
18:    int pos;  
19:  };  
20:    
21:  Cube cubes[maxN];  
22:  CubeSet cs[maxN];  
23:  long long maxHeight[maxN]; //maxHeight[i] = The maximum height of a CubeSet using i cubes 
24:  int maxIdx[maxN];  //maxIdx[i] = The index of CubeSet that can produce maxHeight[i]
25:  bool cubeSort(Cube , Cube);  
26:  bool colorSort(CubeSet, CubeSet);  
27:    
28:  int main()  
29:  {  
30:    int N;  
31:    cin>>N;  
32:      
33:    for(int i=0; i < N; i++)  
34:    {  
35:      cin>>cubes[i].color;  
36:      cin>>cubes[i].height;  
37:      cubes[i].idx = (i+1);  
38:    }  
39:    
40:    sort(cubes, cubes + N, cubeSort);  
41:    
42:    int colorLen = 0; //number of different cube colors  
43:       int j = 0;  
44:    int minH = maxN;  
45:    
46:    for(int i=0; i < N; i=j)  
47:    {  
48:      //Save the position of the first cube of the current color  
49:      cs[colorLen].pos = i;     
50:    
51:      for(j=i; j < N && cubes[j].color == cubes[i].color; j++)  
52:                 cs[colorLen].len++;  
53:    
54:      minH = min(minH, cs[colorLen].len);  
55:      colorLen++;  
56:    }  
57:    
58:    sort(cs, cs + colorLen, colorSort);  
59:    
60:    memset(maxHeight, 0, sizeof(maxHeight));  
61:    memset(maxIdx, 0, sizeof(maxIdx));  
62:      
63:    long long ans = 0;  
64:    int bestA_idx, bestA_len, bestB_idx, bestB_len;  
65:    for(int i=0; i < colorLen; i++)  
66:    {  
67:      long long curH = 0; //total height of current color cubeses  
68:        
69:      for(int j=0; j < minH-1; j++)  
70:        curH += cubes[cs[i].pos + j].height;  
71:        
72:      for(int j = minH-1; j < cs[i].len; j++)  
73:      {  
74:        curH += cubes[cs[i].pos + j].height; //add the current cube  
75:          
76:        //Try two possible heights  
77:        for(int k=j; k <= j+1; k++)  
78:        {  
79:          if(maxHeight[k] != 0 && (curH + maxHeight[k]) > ans)  
80:          {  
81:            ans = curH + maxHeight[k];  
82:            bestA_idx = i, bestB_idx = maxIdx[k];  
83:            bestA_len = j+1, bestB_len = k;  
84:          }  
85:        }  
86:      }  
87:        
88:      /*  
89:       We are done for this color group.  
90:       This may be included in an optimal answer,  
91:       so update maxHeight and maxIdx  
92:       */  
93:      if(curH > maxHeight[cs[i].len])  
94:      {  
95:        maxHeight[cs[i].len] = curH;  
96:        maxIdx[cs[i].len] = i;  
97:      }  
98:    }  
99:      
100:    cout<<ans<<endl;  
101:    cout<<(bestA_len + bestB_len)<<endl;  
102:      
103:    for(int i=0; i < bestA_len; i++)  
104:    {  
105:      cout<<cubes[cs[bestA_idx].pos + i].idx;  
106:      if(i < bestB_len)  
107:        cout<<" "<<cubes[cs[bestB_idx].pos + i].idx<<" ";  
108:    }  
109:    cout<<endl;  
110:  }  
111:    
112:  bool cubeSort(Cube a, Cube b)  
113:  {  
114:    //sort by color  
115:    if(a.color != b.color) return a.color < b.color;  
116:      
117:    //If color is the same, sort by height in non-increasing order  
118:    return a.height > b.height;  
119:  }  
120:    
121:  //Sort cubeSet by its length.  
122:  bool colorSort(CubeSet a, CubeSet b)  
123:  {  
124:    return a.len < b.len;  
125:  }  


I will post editorials on other problems later.

Wednesday, March 7, 2012

SRM 536 DIV2

It's been a long time since I posted my last blog.

Google Code Jam Korea kept me busy, and the fact that the average page view of my blog is about 8 kept me from investing my time to this blog.

Maybe, one day, this blog becomes useful to some people. If anybody happens to visit this blog, please let me know that you were here by leaving comments. Simple "Hi" or "I read your blog. Nice." will be enough.

So many people solved 250 and 500, and so did I.  It looked like fast submission was key to getting a high rank.
After today's SRM, I became blue again, and got back to DIV 1.


250 - BinaryPolynomialDivTwo

Summary
The problem asks us to calculate binary polynomial P for given array of coefficients, A.
If P(x) == 0, it is called "root".
If P(x) == 1, it is not a "root".
We need to find out how many roots are possible, given that x is either 0 or 1.

Approach
Note that pow(0,0) is 1 , and pow(0,i) is 0  for 0 < i
So, P(0) is 1 if and only if A[0] is 1.

For P(1),  pow(i,1) is 1 for all i.
So let sum = number of coefficient 1 in A.
P(1) = (sum%2 == 0) ? 1 : 0;

Source Code
1:    
2:  int BinaryPolynomialDivTwo::countRoots(vector <int> a) {  
3:         
4:    int one = 0;  
5:    int N = a.size();  
6:    int ans = 0;  
7:      
8:    if(a[0] == 0) ans++;  
9:      
10:    REP(i,0,N)  
11:      if(a[i] == 1) one++;  
12:    
13:    if(one%2 == 0) ans++;  
14:      
15:    return ans;  
16:  }  



500 - RollingDiceDivTwo

Summary
Every time we through the same number of dice, and each dice has different number of faces.
Every time we through, we record which face of each dice showed up. However, we do not record them in order.
For example, we may record faces of
(dice1, dice2, dice3)   in the first round, but
(dice3, dice1, dice2)  in the second round.
In other words, the order of faces of dices written are completely random.
We need to find the minimum possible total number of faces of all dice.


Approach
We can use greedy algorithm.
1. For each recorded round, sort the record in non-decreasing order.
    Now, for each round i, rolls[i][0] <= rolls[i][1] <= rolls[i][2] ..

2. For each "column," find the maximum number of faces.

3. The maximum number of faces for each record column is the minimum possible total number of faces.


Source Code
1:    
2:  int RollingDiceDivTwo::minimumFaces(vector <string> rolls) {  
3:         
4:    int R = rolls.size();  
5:    int C = rolls[0].size();  
6:      
7:    vector< vector<int> > dice(R, vector<int>(C,0));  
8:      
9:    vector<int> maxFace(C,0);  
10:      
11:    REP(i,0,R)  
12:    {  
13:      REP(j,0,C)  
14:      {  
15:        dice[i][j] = (rolls[i][j] - '0');  
16:      }  
17:      sort(dice[i].begin(), dice[i].end());  
18:      REP(j,0,C)  
19:      {  
20:        maxFace[j] = max(maxFace[j], dice[i][j]);  
21:      }  
22:    }  
23:    int ans = 0;  
24:    REP(i,0,C)  
25:    ans += maxFace[i];  
26:      
27:    return ans;  
28:  }  
29:    




1000 - MergersDivTwo
Summary
Problem statement is very direct about what it wants. I think the statement itself is good enough.


Approach
The problem is asking for "maximum possible revenue of the final company that can be created in any series of mergers that joins all the companies."
Note the words "maximum" and "possible."
Do you see that it can be rephrased as "pick the maximum value among all possible outcomes" ?

Now, it is clear that we need to try all possible values, but also note that we can be "smart" about trying possible outcomes if we make some observations.

Observation 1 : It is always better to merge companies with higher revenue later.
Reasoning.
Say we are merging K companies from c1 to cK.
And let maxC = company with maximum revenue in [c1,cK]
             RES = result revenue after merging operation
Then it is obvious that after merging operation, RES <= maxC.
Also note that RES will be lower if elements in [c1, cK] have lower values.
So, in order to maximize RES, we want the revenues of companies included in merging operation to be as high as possible.
Let T = the last merging operation.
Then to maximize our answer, companies in T must have highest revenues possible.
Then the company with the maximum revenue should be included in T,
and the company should not be included in previous merges because it will decrease its revenue.
Keep applying the same logic to the company with the second highest revenue, and so on.


From observation 1, we know that we have to sort companies in non-decreasing order by their revenues.

Now, what we are not sure about is how many companies we should merge at each step. After all, the problem statement said we need AT LEAST K companies to merge, not EXACTLY K companies.
This is where "trying all possible values" come in.
We try merging C companies where K <= C.
And, we use array to store the current best value.

I explained the procedure in detail in my source, using comments.


Source Code

1:  double MergersDivTwo::findMaximum(vector <int> revs, int K) {  
2:         
3:    int N = revs.size();  
4:    double dp[N]; //d[i] = best result after merging operations [0...i]  
5:    double sum[N]; //sum[i] = sum of revenues inside [0, i]  
6:    sort(revs.begin(), revs.end());  
7:      
8:    sum[0] = revs[0];  
9:    for(int i=1; i < N; i++)  
10:      sum[i] = sum[i-1] + revs[i];  
11:      
12:    for(int i = K-1; i < N; i++)  
13:      dp[i] = sum[i] / double(i+1); //default value is merging [0,i] companies altogether.  
14:      
15:    /*  
16:     At each iteration, we have two options  
17:     1) Keep the current dp[j]  
18:     2) Merge all companies between [i,j]   
19:     Note that after first merge, we always one "previously merged company",   
20:     so we only need (K-1) companies to meet the minimum company number requirement.  
21:     */  
22:    for(int i = K; i < N; i++)  
23:      for(int j = i + (K-1) - 1, cnt = K; j < N; j++, cnt++)  
24:        dp[j] = max(dp[j], (dp[i-1] +sum[j] - sum[i-1])/double(cnt));  
25:      
26:    return dp[N-1];  
27:  }  
28:    




Tuesday, February 14, 2012

(The difference of two squares) mod 4

Argument
: Let a , b any integer, and z = a^2 - b^2
Then (z mod 4) can never be 2 for any value of a and b.

Proof
Lemma 1.  (A perfect square mod 4 ) is always either 0 or 1.
Proof for lemma 1.
Let k = any number.
Then 2k is an ever number, and (2k+1) is an odd number.
Square of 2k = 4k^2 ==>  (4k^2) mod 4 = 0
Square of (2k+1) = (4k^2 + 4k + 1) ==> (4k^2 + 4k + 1) mod 4 = 1

From lemma 1, we can conclude that (a^2 - b^2) can one of following four cases
1) even^2 - even^2
2) even^2 - odd^2
3) odd^2 - even^2
4) odd^2 - odd^2
where even = an even number and odd = an odd number.

Taking mod 4 for each case,
1) (0 - 0) mod 4 => 0
2) (0 - 1) mod 4 => 3
3) (1 - 0) mod 4 => 1
4) (1 - 1) mod 4 => 0

Therefore, for all possible cases, taking mod 4 of difference of squares always result in 0,1, or 3, but never 2.

Monday, February 13, 2012

Dynamic Programming with profile - example 3

This is a problem from TopCoder SRM 383 DIV1 - 500 point.

Here is a link to the problem statement : http://community.topcoder.com/stat?c=problem_statement&pm=8397&rd=10806

Here is the problem statement.

Problem Statement

     You are building a house and are laying the floorboards in one of the rooms. Each floorboard is a rectangle 1 unit wide and can be of any positive integer length. Floorboards must be laid with their sides parallel to one of the sides of the room and cannot overlap. In addition, the room may contain features such as pillars, which lead to areas of the floor where no floorboards can be laid. The room is rectangular and the features all lie on a unit-square grid within it. You want to know the minimum number of floorboards that you need to completely cover the floor.

You are given a vector <string> room containing the layout of the room. Character j in element i of room represents the grid-square at position (i, j) and will be a '.' if this square needs to be covered with a floorboard or a '#' if the square is a feature where no floorboard can be laid. Return an int containing the minimum number of floorboards you need to completely cover the floor.

Definition

    
Class: FloorBoards
Method: layout
Parameters: vector <string>
Returns: int
Method signature: int layout(vector <string> room)
(be sure your method is public)
    

Constraints

- room will contain between 1 and 10 elements, inclusive.
- Each element of room will contain between 1 and 10 characters, inclusive.
- Each element of room will contain the same number of characters.
- Each character in room will be a '.' or a '#'.

Examples

0)
    
{"....."
,"....."
,"....."
,"....."
,"....."}
Returns: 5
5 boards laid side-by-side will cover this square room.
1)
    
{"......."
,".#..##."
,".#....."
,".#####."
,".##..#."
,"....###"}
Returns: 7
A possible optimal layout could look like the following

|------
|#--##|
|#----|
|#####|
|##--#|
|---###


Each floorboard is represented by a consecutive horizontal sequence of '-' characters or a consecutive vertical sequence of '|' characters.
2)
    
{"####"
,"####"
,"####"
,"####"}
Returns: 0
This is a strange room, with no floor area to cover.
3)
    
{"...#.."
,"##...."
,"#.#..."
,".#...."
,"..#..."
,"..#..#"}
Returns: 9
An optimal pattern here is:

---#||
##--||
#-#|||
|#-|||
||#|||
||#||#
4)
    
{".#...."
,"..#..."
,".....#"
,"..##.."
,"......"
,".#..#."}
Returns: 9



Approach
Let
R = number of rows in room.
C = number of columns in room.
Constraints tell us that 1 <= R,C <= 10

From small constraints, we can try the following approach :
Go through each row and try every possible combination.
Each row has at most 10 columns. So 2^10 maximum combination.
There are at most 10 rows.
So at worst, it will take 10 * 2^10 = 10 * 1024 = 10,240 operations.
Formally, runtime is O(R * 2^C)

Let's define state, recurrence, and profile.

State
dp[idx][profile]
= minimum number of boards we can use to cover all the areas from row[0] to row[idx] 
   when (idx-1)th row has configuration of profile.

Recurrence
The shape of each row depends on the shape of the previous row.

Profile
Binary representation of the previous row.
(i)th bit of profile is set if (i)th column of the row is a part of a vertical strip.
0 otherwise( which means it is either a pillar or a part of a horizontal strip).



Source code

class FloorBoards {
public:
int layout(vector <string>);
};

int R;
int C;
int mem[11][1<<10];
vector<string> room;
/*
 "mask" is a binary number representing shape of the previous row.
 (i)th bit of mask is set to 1 if (i)th column of the previous row is a part of a vertical strip.
 0 otherwise.
 */

int rec(int idx, int mask)
{
    if(idx == R) return 0;
    
    int& res = mem[idx][mask];
    if(res!=-1) return res;
    
    res = INF;
    
    for(int i=0; i < (1<<C); i++)
    {
        //check validity of the current config
        bool isValid = true;
        for(int j=0; j < C; j++)
        {
            if( ((i>>j) & 1) && ( room[idx][j] == '#') )
            {
                isValid = false;
                break;
            }
        }
        if(!isValid) continue;
        
        //Count the number of vertical and horizontal strips to use for the current row.
        int vert = 0;
        int hori = 0;
        bool isHori = false;
        for(int j=0; j < C; j++)
        {
            /*
             It is a vertical strip.
             If the current column of the above row is also a part of a vertical strip,
             the current square is a part of the continuation of the previous vertical strip.
             Only use a new vertical strip if the previous column is not a part of a vertical strip.
             */
            if( (i>>j) & 1)
            {
                isHori = false;
                if( ((mask>>j) & 1) == 0 )
                    vert++;
            }
            else // The current square is either a pillar or a horizontal strip
            {
                if(room[idx][j] == '#') //It is a pillar
                {
                    isHori = false;
                }
                else //It is a horizontal strip
                {
                    if(!isHori)
                    {
                        isHori = true;
                        hori++;
                    }
                }
            }
        }
        res = min(res, vert + hori + rec(idx + 1, i));
    }
    return res;
}

int FloorBoards::layout(vector <string> _room) {
    room = _room;
    R = room.size();
    C = room[0].size();
    memset(mem, -1, sizeof(mem));
    
return rec(0,0);
}

Dynamic Programming with profile - example 2

This is a problem from TopCoder SRM 444 DIV1 - 500 point.

Here is a link to the problem statement : http://community.topcoder.com/stat?c=problem_statement&pm=10487


Approach
Let
R = number of rows
C = number of Columns
Constraints tell us that 1 <= R <= 10, 1 <= C <= 25

We can go through each column, try all configurations for the current column, and choose the best outcome.
Runtime will be O(C * 2^R)  => Worst case : 25 * 2^10 = 25 * 1024 = 25600 . Small enough

You can also go through each row, try all configurations for the current row, and choose the best outcome.
Runtime will be O(R * 2^C) => Worst case : 10 * 2^25 = 10 * 33,554,432 = 335,544,320. Too big.

So let's go with iterating each column.


Algorithm

State
dp[idx][profile]
= maximum outcome for (idx)th column when its previous column has configuration of "profile"

recurrence
The validity of configuration of the current column depends on the configuration of the previous column.

Profile
A binary number representing configuration of the previous column.
(i)th bit of profile is set to 1 if (i)th row of the previous column is topleft corner of 2x2 '4' block.


Coding
I explained all the little details in comments in my source code.


Source Code

int INF = numeric_limits<int>::max();
int nINF = numeric_limits<int>::min();
typedef long long ll;

class FourBlocks {
public:
int maxScore(vector <string>);
};

int mem[25][1<<10];
int R;
int C;
vector<int> cols;

/*
 Count number of bits in "mask"
 */
int countTopLeftFour(int mask)
{
    int res = 0;
    while(mask > 0)
    {
        if(mask & 1) res++;
        mask = mask>>1;
    }
    return res;
}

/*
 rec(idx,mask) returns maximum outcome for (idx)th column
 when its previous column has configuration of "mask"
 (i)th bit of mask is set to 1 
 if ith row of the previous column is topleft corner of 2x2 '4' block.
 */
int rec(int idx, int mask)
{
    if(idx == C)
    {
        if(mask == 0) return 0;
        return nINF;
    }
    
    if(mask & cols[idx]) return nINF;
    
    int& res = mem[idx][mask];
    if(res != -1) return res;
    
    res = 0;

    /*
     Try every combination for current column.
     For each combination, check if it is valid by looking at "mask"
     and grid
     */
    for(int config = 0; config < (1<<(R-1)); config++)
    {
        /*
         "config" works the same way as "mask"
         We only check config to (1<<(R-1) - 1) instead of (1<<R - 1)
         because we cannot put the topleft corner of 2x2 '4' block
         at the last row.
         
         Let's call the current cell we are looking at
         cell[j][idx]  where idx stays the same for this column,
         and this information is strored at jth bith of config.
         
         We will check following conditions
         1. Is cell[j+1][idx] empty?
         
         2. Is cell[j][idx-1] or cell[j+1][idx-1] the beginning of
         4x4 block, thus making cell[j][idx] and cell[j+2][idx] unsuitable
         locations for the beginning of 4x4 block?
         
         3. Is either cell[j][idx] or cell[j+1][idx] supposed to be '1'?
         */
        
        if( (config & (config<<1)) || (mask & (config | (config<<1))) || (cols[idx] & (config | (config<<1))) )
            continue;
        
        //Pick the best outcome out of all valid configurations
        res = max(res, rec(idx+1, config | (config<<1)) + countTopLeftFour(config) * 16 + (R - countTopLeftFour(mask | config | (config<<1))));
    }
    
    return res;
    
}


int FourBlocks::maxScore(vector <string> grid) {
R = grid.size();
    C = grid[0].size();
    
    memset(mem, -1, sizeof(mem));
    
    /*
     cols[i] 
     = jth bit of cols[i] is set to 1 
       if row j of column i contains '1' block in grid.
       0 otherwise.
     */
    for(int c = 0; c < C; c++)
    {
        int mask = 0;
        for(int r = 0; r < R; r++)
        {
            if(grid[r][c] == '1')
                mask = mask | (1<<r);
        }
        cols.push_back(mask);
    }
    
    return rec(0,0);
    
}