Saturday, March 31, 2012

TopCoder Open 2012 - Round 1A

I submitted my solution for 250 and 1000.
Only my 250 survived the system test.
I figured out how to solve 500 after the contest by looking at other people's codes.
Getting a score of 203.56, I got 831st place.  I failed.

250 - EllysJuice
Apporoach
We need to look at this problem by dividing it into two cases.
Let N = number of different names.

case 1) N = 1, or there is only one player.
They this player always wins.

case 2) N >= 2, or there is at least two different players.
Note that we are only interested in the amount of juice we drink, not whether it is an apple or orange.
So, focusing on the amount, playing the first turn gives you 0.5 juice, and playing the second turn give you another 0.5 juice.

Let Ci = number of turns that player i plays.
If Ci = 1,  then player i can drink at most 0.5 juice.
Since N>=2, at least one other player can drink 0.5 juice by playing the second turn.

So if Ci = 1, player i CANNOT win because there is least one player who can drink more than or equal amount of juice.
If Ci >=2,  player i can play the first turn AND the second turn, which gives player i a chance to drink 1.0 juice.  Notice that even if a single player plays infinite turns after 2nd turn,  there is no way he can drink exactly 1.0 juice because the player drinks half of what is left every turn, never quite drinking the entire juice left. In other words, once a player plays first and second turn, the player is guaranteed to win.

So here are basic steps
1. Count how many players there are
2. If there is a single player, return his name.
3. If there are more than one player, count how many turns each player gets to play.
4. Return all the names of players who play at least 2 turns.


Source Code
1:    
2:  bool sortNames(string, string);  
3:    
4:  vector <string> EllysJuice::getWinners(vector <string> players) {  
5:    
6:    vector<string> res;  
7:      
8:    set<string> names;  
9:    map<string, int> ct;  
10:      
11:    for(int i=0; i < players.size(); i++)  
12:    {  
13:      if(names.count(players[i]) == 0)  
14:      {  
15:        names.insert(players[i]);  
16:        ct[players[i]] = 1;  
17:      }  
18:      else  
19:        ct[players[i]]++;  
20:    }  
21:        
22:    int A = names.size();  
23:      
24:    if(A == 1) return vector<string>(names.begin(), names.end());  
25:      
26:    set<string>::iterator itr;  
27:    for(itr = names.begin(); itr != names.end(); itr++)  
28:      if(ct[(*itr)] >= 2) res.push_back(*itr);  
29:    
30:    sort(res.begin(), res.end(), sortNames);  
31:    return res;  
32:  }  
33:    
34:  bool sortNames(string a, string b)  
35:  {  
36:    for(int i=0; i < min(a.size(),b.size()); i++)  
37:      return a[i] < b[i];  
38:      
39:    return a.size() < b.size();  
40:        
41:  }  
42:    



500 - EllysFractions
Apporoach
Main Algorithm
:  Given factorial number NF, How many irreducible fractions have the form (A/B) that satisfy (A*B = NF) ?

Let's revisit the definition of factorial number.
Factorial number F! = 1*2*3*4*5*...*(F-1)*F

We have to divide factors of (I) into two groups so that one goes to group A, and the other goes to group B.
Notice that we want A/B to be IRREDUCIBLE.
If you think about it, you will realize that diving factors of F is actually diving PRIME factors of F because if two numbers that share a common prime number goes to different groups, their common prime number will be canceled out, resulting in A * B != F!

For example,
Let F = 1*2*3*4*5 , or 5!

2 and 4 share a common prime number , which is 2.

Let's put them in a different group, Say A = 1*2*3 and B = 4*5
Then A/B = (1*2*3) / (4*5)  = (1*3)/(2*5) , and A*B != F because we cancelled out 2 from A and B to make A/B irreducible.

There is one more condition to match
:  A has to be smaller than B,  or A < B.

Keeping the condition in mind, our answer to the main algorithm is
(number of ways to divide primes in [1 from F (inclusive)] into two groups)  /  2

We divide by 2 because for each unique group pair (A,B)  only A/B is allowed, not B/A.

More formally,
let P = number of primes in [1 .. F]
ans = (2^P) / 2

The problem statement asks us to find group A/B such that A*B = one of the factorial numbers from 1! to N!
Now that we know how to find number of A/B  such that A*B = a given factorial number,
all we need to do is ask the question from 1 to N, inclusive.

Source Code
1:    
2:  const int MAX = 300;  
3:  bool isPrime[MAX];  
4:    
5:  long long EllysFractions::getCount(int N) {  
6:      
7:    memset(isPrime, true, sizeof(isPrime));  
8:      
9:    ll res = 0;  
10:    ll cur = 1ll;  
11:    for(int i=2; i <= N; i++)  
12:    {  
13:      if(isPrime[i])  
14:      {  
15:        for(int j= i+i; j <= N; j+=i)  
16:          isPrime[j] = false;  
17:        cur *= 2ll;  
18:      }  
19:      res += (cur/2ll);  
20:    }  
21:         
22:    return res;  
23:      
24:  }  


1000 - EllysLights

You need to tackle this problem after making careful observations.
During the contest, I noticed the problem could be turned into bitmask problem.
To illustrate,  bulb sequence "NYYNN" can be turned into "01100",
and a switch "NNYYN" can be turned into ""00110".
Using a switch has the same effect as doing xor operation between a switch and bulb sequence.
For example, if we use the switch from the above example,
result will be (01100 ^ 00110) = 01010 ,  or "NYNYN"

The above observation is correct, but one should look a little bit further to solve this problem.
Unfortunately, this is how far I got during the contest.
Out of desperation, I used Breadth First Search with memorization to try every possible combination of sequences. My approach is okay for N <= 35~40.  However, the problem states that N <= 50.
Consequently I failed the system test.

After the contest, I went to the practice room and read rng_58's solution to this problem.
 I soon realized I failed to make a key observation that was crucial to solving this problem.
Following approach and source code was obtained by carefully reading rng_58's code (i.e. this is not my original idea)


Apporoach
In my opinion, reading the problem statement carefully was key to solving this problem.
The key sentence was "A BULB IS CONNECTED TO AT MOST TWO SWITCHES."
Let's call this sentence THE SWITCH RULE.

From the switch rule, we can categorize each switch X as follow

Case 1) Switch X is the only switch that controls (i)th bulb.
      sub 1) If (i)th bulb is on, we MUST USE switch X.
      sub 2) If (i)th bulb is off, we MUST NOT USE switch X.


Case 2) It is one of two switches that control (i)th bulb.
      sub 3) If (i)th bulb is on, only one of the two switches should be used.
      sub 4) If (i)th bulb is off, either use both switches or do not use any of them at all.

Now it is time to apply our observations to the problem.

Step 1)
For each switch X that MUST BE USED (sub 1), turn switch X on, and turn other switches that need to be turned on with switch X(sub 3).  Note theses two facts
1) Each switch can control to multiple bulbs.
2) Turn switch Y on because of switch X  may result in other switches that are not related to X but related to Y.
So we need to turn on all switches that are related to switch X and all other switches that are either related to switch X  or switches that are related to X.
Also do not forget that we have to turn off all switches that belong to sub 4

We can use depth first search to do this.

Step 2)
For each switch X that MUST NOT BE USED(sub 2), turn switch X off, and
turn off switches belonging to sub 3, and
turn on switches belonging to sub 4.

Step 3)
It is possible that switches that have no relationship to switches in Step 1 and Step 2 exist. In other words, we can be still uncertain what to do with some switches after doing Step 1 and Step 2. Sample case 0 represents this situation.
For all these switches, figure out relationships among them.

Step 4)
Now that we figured out what to do with all switches, check if our conclusion conforms to our initial observations (sub 1, sub 2, sub 3, and sub 4). If it does not, return -1.

Step 5)
Our answer is (number of switches that are turned on in Step 1 and Step 2) + (minimum number of switches that should be turned in Step 3).
Count these switches, and return them.

I will post two source codes. The first one is a successful code that implements what I explained so far. The second one is what I wrote during the contest. I am posting my failed code hoping that somebody may come up with a clever way to improve my solution enough to pass system test.

Source Code - Successful
1:  const int maxN = 50;  
2:  int N;  
3:  bool on[maxN];  
4:  bool off[maxN];  
5:  bool same[maxN][maxN];  
6:  bool diff[maxN][maxN];  
7:  int groupNum[maxN];  
8:  int groupCnt[maxN*2];  
9:    
10:  void dfs(int, int);  
11:    
12:  int EllysLights::getMinimum(string bulb, vector <string> S) {  
13:         
14:    memset(groupNum, -1, sizeof(groupNum));  
15:    memset(groupCnt, 0, sizeof(groupCnt));  
16:    N = S.size();  
17:      
18:    for(int i=0; i < bulb.size(); i++)  
19:    {  
20:      int X = -1; //First switch that controls (i)th bulb  
21:      int Y = -1; //Second switch that controls (i)th bulb  
22:      for(int j=0; j < N; j++)  
23:        if(S[j][i] == 'Y')  
24:        {  
25:          if(X == -1) X = j;  
26:          else Y = j;  
27:        }  
28:        
29:      if(bulb[i] == 'Y')  
30:      {  
31:        if(X != -1)  
32:        {  
33:          if(Y != -1) diff[X][Y] = diff[Y][X] = true;  
34:          else on[X] = true;  
35:        }  
36:        else  
37:        {  
38:           //No switch controls this bulb, but this bulb  
39:           //needs to be turned off. It is impossible.  
40:          return -1;  
41:        }  
42:      }  
43:      else //bulb[i] == 'N'  
44:      {  
45:        if(X != -1)  
46:        {  
47:          if(Y != -1) same[X][Y] = same[Y][X] = true;  
48:          else off[X] = true;  
49:        }  
50:      }  
51:    }  
52:    
53:    for(int i=0; i < N; i++)  
54:    {  
55:      if(on[i]) dfs(i, 1); //Step 1  
56:      if(off[i]) dfs(i, 0); //Step 2  
57:    }  
58:      
59:    //step 3  
60:    int extra = 2;  
61:    for(int i=0; i < N; i++)  
62:      if(groupNum[i] == -1)  
63:      {  
64:        dfs(i, extra);  
65:        extra+=2;  
66:      }  
67:      
68:    //step 4  
69:    for(int x=0; x < N; x++)  
70:    {  
71:      if(on[x] && groupNum[x] != 1) return -1;  
72:      if(off[x] && groupNum[x] != 0) return -1;  
73:      for(int y=0; y < N; y++)  
74:      {  
75:        if(same[x][y] && groupNum[x] != groupNum[y]) return -1;  
76:        if(diff[x][y] && groupNum[x] == groupNum[y]) return -1;  
77:          
78:      }  
79:    }  
80:      
81:    //step 5  
82:    for(int i=0; i < N; i++)  
83:      groupCnt[groupNum[i]]++;  
84:      
85:    int ans = groupCnt[1];  
86:    for(int i=2; i< extra; i+=2)  
87:      ans += min(groupCnt[i], groupCnt[i+1]);  
88:    
89:    return ans;  
90:  }  
91:    
92:  void dfs(int X, int id)  
93:  {  
94:    if(groupNum[X] != -1) return;  
95:    groupNum[X] = id;  
96:      
97:    for(int i=0; i < N; i++)  
98:    {  
99:      if(same[X][i]) dfs(i, id);  
100:      if(diff[X][i]) dfs(i, id^1);  
101:    }  
102:      
103:  }  


Source Code - Failed

1:  ll getVal(string);  
2:    
3:  ll initVal;  
4:  vector<ll> swit;  
5:  set<ll> chk;  //memoization
6:    
7:  int EllysLights::getMinimum(string initial, vector <string> switches) {  
8:         
9:    int N = switches.size();  
10:    initVal = getVal(initial);  
11:    for(int i=0; i < N; i++)  
12:      swit.push_back(getVal(switches[i]));  
13:      
14:    if(initVal == 0) return 0;  
15:      
16:    int ans = 55;  
17:      
18:    queue<ll> Q;  
19:      
20:    for(int i=0; i < N; i++)  
21:    {  
22:      chk.insert(swit[i]);  
23:      Q.push(swit[i]);  
24:      Q.push(1ll<<i);  
25:      Q.push(1);  
26:    }  
27:      
28:    //check validity  
29:    for(int i=0; i < initial.size(); i++)  
30:    {  
31:      if(initial[i] == 'Y')  
32:      {  
33:        bool hasY = false;  
34:        for(int j=0; j < N; j++)  
35:        {  
36:          if(switches[j][i] == 'Y')  
37:          {  
38:            hasY = true;  
39:            break;  
40:          }  
41:        }  
42:        if(!hasY) return -1;  
43:      }  
44:    }  
45:      
46:    //BFS  
47:    while(!Q.empty())  
48:    {  
49:      ll val = Q.front(); Q.pop();  
50:      ll condi = Q.front(); Q.pop();  
51:      ll turn = Q.front(); Q.pop();  
52:        
53:      if(val == initVal)  
54:      {  
55:        ans = turn;  
56:        break;  
57:      }  
58:        
59:      for(int i=0; i < N; i++)  
60:      {  
61:        if( (condi & (1ll<<i)) == 0 )  
62:        {  
63:          ll next = val ^ swit[i];  
64:          if(chk.count(next) == 1) continue;  
65:          chk.insert(next);  
66:          Q.push(next);  
67:          Q.push(condi |= (1ll<<i) );  
68:          Q.push(turn + 1);  
69:            
70:        }  
71:      }  
72:    }  
73:    
74:    if(ans == 55) return -1;  
75:    return ans;  
76:  }  
77:    
78:  ll getVal(string str)  
79:  {  
80:    ll res = 0ll;  
81:    ll two = 1ll;  
82:    for(int i=str.size()-1; i >= 0; i--)  
83:    {  
84:      if(str[i] == 'Y') res += two;  
85:      two *= 2ll;  
86:    }  
87:    return res;  
88:  }  
89:    



Tuesday, March 27, 2012

Codeforces #114

I participated in DIV2.
It was a disaster for me. After 14 minutes, I solved A and moved on to B.
Since then, I got stuck on problem B because I was sure of my answer but I kept failing to pass pretest 3.
After the contest, I found out that I didn't put "End of Line" character  AT THE END of the output.
It was a dumb mistake.
I looked at C, and found out it was a rather easy problem if you are familiar with "area under velocity - time  graph.'

Here are my solutions. I added explanations in comments.

A - Wizards and Demonstration
Source Code
1:  int main()  
2:  {  
3:    ios_base::sync_with_stdio(false);  
4:      
5:    int N,X,Y;  
6:    cin>>N>>X>>Y;  
7:      
8:    int up = Y*N;  
9:    if( (up % 100) > 0 )  
10:    {  
11:      up -= (up % 100);  
12:      up += 100;  
13:    }  
14:    up /= 100;  
15:    cout<< max(0, up - X) <<endl;  
16:    
17:    return 0;  
18:  }  




B - Wizards and Minimal Spell
Source Code
1:  bool isSpell(string);  
2:  void printBuf(string);  
3:    
4:  int main()  
5:  {  
6:    ios_base::sync_with_stdio(false);  
7:    string emp = "" + char(15);  
8:    string buf = emp;  
9:      
10:    string str;  
11:    while(getline(cin,str))  
12:    {  
13:      if(isSpell(str))  
14:      {  
15:        if(buf != emp) printBuf(buf);  
16:        cout<<str<<endl;  
17:        buf = emp;  
18:      }  
19:      else  
20:      {  
21:        if(buf == emp) buf = str;  
22:        else buf += str;  
23:      }  
24:    }  
25:      
26:    if(buf != emp)  
27:      printBuf(buf);  
28:    
29:    return 0;  
30:  }  
31:    
32:  bool isSpell(string str)  
33:  {  
34:    stringstream ss(str);  
35:    string test;  
36:    ss>>test;  
37:    return (test[0] == '#');  
38:  }  
39:    
40:  void printBuf(string buf)  
41:  {  
42:    stringstream ss(buf);  
43:    string cur;  
44:    while(ss>>cur)  
45:      cout<<cur;  
46:    cout<<endl;  
47:  }  
48:    







C - Wizards and Trolleybuses
Source Code
1:    
2:  int main()  
3:  {  
4:    ios_base::sync_with_stdio(false);  
5:      
6:    int N;  
7:    double A,D;  
8:    cin>>N>>A>>D;  
9:      
10:    double prev = 0.0;  
11:      
12:    cout<<fixed;  
13:    cout.precision(9);  
14:    for(int i=1; i <= N; i++)  
15:    {  
16:      double t, v;  
17:      cin>>t;  
18:      cin>>v;  
19:      double t_stop = sqrt(2.0*D/A); //time when train arrives at the destination  
20:      double t_max = v / A; //time when train reaches full allowed speed  
21:        
22:      double t_ans = t; //train starts at time t  
23:      //Arrive at the destination before reaching full speed, or not.  
24:      if(t_stop < t_max) t_ans += t_stop;  
25:      else t_ans += t_max + D/v - v/(2.0*A);  
26:        
27:      t_ans = max(prev, t_ans); //cannot arrive before the previous train  
28:      cout<<t_ans<<endl;  
29:      prev = t_ans;  
30:    }  
31:      
32:    return 0;  
33:  }  









D - Wizards and Huge Prize

Note
The problem statement was a bit confusing to me.

I initially thought that "player" cannot gain a huge prize if "the current bag size" is not big enough.
What the question really meant was that "player cannot bring "all huge prizes" if "the bag size at the end of the entire tour" is not big enough.
For example, at example 1,
the player starts with no bag, and the first and second round has huge prizes.
I thought it was impossible for him to earn any of the prizes.
However, he can earn those "huge prizes" first, and then win a bag at the third round.
So we are only interested at the bag size at the end of the entire tours.



Source Code

1:  const int maxN = 210;  
2:    
3:  /*  
4:   dp[i][j][k]  
5:   = probability of winning j tours in first i days  
6:    with bag capacity k and carrying all won prizes  
7:   */  
8:  double dp[maxN][maxN][maxN];  
9:    
10:  int main()  
11:  {  
12:    ios_base::sync_with_stdio(false);  
13:      
14:    int N,L,K;  
15:    cin>>N>>L>>K;  
16:      
17:    double p[N+1];  
18:    int a[N+1];  
19:      
20:    p[0] = 1.0;  
21:    for(int i=1; i <= N; i++)  
22:    {  
23:      cin>>p[i];  
24:      p[i] /= 100.0;  
25:    }  
26:      
27:    a[0] = K;  
28:    for(int i=1; i <= N; i++)  
29:    {  
30:      cin>>a[i];  
31:      a[i]++;  
32:    }  
33:      
34:    //base case  
35:    dp[0][0][K] = 1.0;  
36:      
37:    for(int i=0; i < N; i++)  
38:      for(int j=0; j <= i; j++)  
39:        for(int k=0; k <= 200; k++)  
40:          if(dp[i][j][k] > 0.0)  
41:          {  
42:            dp[i+1][j+1][min(200, k+a[i+1])] += dp[i][j][k] * p[i+1];  
43:            dp[i+1][j][k] += dp[i][j][k] * (1.0 - p[i+1]);  
44:          }  
45:     
46:     
47:    double ans = 0.0;  
48:    for(int j=L; j <= 200; j++)  
49:      for(int k=j; k <= 200; k++)  
50:        ans += dp[N][j][k];  
51:      
52:    cout<<fixed;  
53:    cout.precision(9);  
54:      
55:    cout<<ans<<endl;  
56:      
57:    return 0;  
58:  }  



Friday, March 23, 2012

Codeforces Round #113 (div. 2)

A - Rank List
Approach
1. Sort the given scores as described in the problem.
2. Find out the score at Kth place
3. Go through the array, count how many scores are same to the score at Kth place.

Source Code
1:  bool sortScore( pair<int,int> a, pair<int,int> b)  
2:  {  
3:    if(a.first != b.first) return a.first > b.first;  
4:    return a.second < b.second;  
5:  }  
6:    
7:  int main()  
8:  {  
9:    ios_base::sync_with_stdio(false);  
10:      
11:    int N,K;  
12:    cin>>N>>K;  
13:      
14:    vector< pair<int,int> > score(N);  
15:      
16:    REP(i,0,N)  
17:    {  
18:      int p,t;  
19:      cin>>p>>t;  
20:      score.push_back( make_pair(p,t) );  
21:    }  
22:    
23:    sort( score.begin(), score.end(), sortScore );  
24:      
25:    pair<int,int> sc = score[K-1];  
26:    int same = 0;  
27:    for(int i=0; i < N; i++)  
28:      if(score[i] == sc)  
29:        same++;  
30:      
31:    cout<<same<<endl;  
32:      
33:    return 0;  
34:  }bool sortScore( pair<int,int> a, pair<int,int> b)  
35:  {  
36:    if(a.first != b.first) return a.first > b.first;  
37:    return a.second < b.second;  
38:  }  
39:    
40:  int main()  
41:  {  
42:    ios_base::sync_with_stdio(false);  
43:      
44:    int N,K;  
45:    cin>>N>>K;  
46:      
47:    vector< pair<int,int> > score(N);  
48:      
49:    REP(i,0,N)  
50:    {  
51:      int p,t;  
52:      cin>>p>>t;  
53:      score.push_back( make_pair(p,t) );  
54:    }  
55:    
56:    sort( score.begin(), score.end(), sortScore );  
57:      
58:    pair<int,int> sc = score[K-1];  
59:    int same = 0;  
60:    for(int i=0; i < N; i++)  
61:      if(score[i] == sc)  
62:        same++;  
63:      
64:    cout<<same<<endl;  
65:      
66:    return 0;  
67:  }  



C- Median
Approach

1. Find X.
   If X does not exist, add X to the given array.

2. Sort the array.

3. Find the median.
      If the median < X, add a number to the right.
      If the median > X, add a number to the left.

4. Do number 3 until the median == X

The above step takes
O(NlogN + N) => O(NlogN)

Source Code
1:  int main()  
2:  {  
3:    ios_base::sync_with_stdio(false);  
4:      
5:    int N, X;  
6:    cin>>N>>X;  
7:      
8:    deque<int> arr(N);  
9:    REP(i,0,N)  
10:    cin>>arr[i];  
11:      
12:    int ans = 0;  
13:      
14:    if(count(arr.begin(), arr.end(), X) == 0)  
15:    {  
16:      ans++;  
17:      arr.push_front(X);  
18:    }  
19:      
20:    sort(arr.begin(), arr.end());  
21:    
22:    int mid = ((arr.size() + 1) / 2) - 1;  
23:      
24:    while(arr[mid] != X)  
25:    {  
26:      if(arr[mid] < X) arr.push_back(0);  
27:      else arr.push_front(0);  
28:      mid = ((arr.size() + 1) / 2) - 1;  
29:      ans++;  
30:    }  
31:      
32:    cout<<ans<<endl;  
33:    
34:    return 0;  
35:  }  



D - Shoes Store

Approach
There are two approaches
1) Greedy Algorithm
2) Dynamic Programming

Greedy Algorithm
 Goal : Sell as many highest priced shoes as possible.
Steps.
1. Sort shoes by size
2. For each shoe, find out which customers can buy it.
3. Sort shoes by cost
4. For each shoe, do follow
   1) See if a customer who can buy the current shoe has already bought a shoe or not.
   2) If not, sell this shoe to the current customer
   3) If yes,  let j  = the shoe that the current customer bought.
       See if any other customer can buy shoe j.
       If yes, sell the current shoe to the current customer.
       If not, move on to the next customer.

Dynamic Programming
- Coming soon

Source Code - Greedy Algorithm
1:  /*  
2:   This code belongs to Avalenche.  
3:   I only placed comments and change variable names   
4:   to help me better understand his code.  
5:   */  
6:  const int maxN = 100010;  
7:    
8:  struct obj  
9:  {  
10:    int cost, size, id;  
11:    obj(int c = -1, int sz = -1, int idx = -1): cost(c), size(sz), id(idx) {}  
12:  };  
13:    
14:  bool compSize(obj a, obj b)  
15:  {  
16:    return a.size < b.size;  
17:  }  
18:    
19:  bool compCost(obj a, obj b)  
20:  {  
21:    return a.cost > b.cost;  
22:  }  
23:    
24:    
25:  //waitList[i] = waiting list of customer ids who can buy shoe i  
26:  vector<int> waitList[maxN];  
27:    
28:  //purchase[i] = shoe id that customer i actually purchased  
29:  //It is -1 if customer i did not purchase anything  
30:  int purchase[maxN];  
31:    
32:  //sold[i] = true if shoe i is sold  
33:  bool sold[maxN];   
34:    
35:  //returns true if shoe x is successfully purchased  
36:  bool buy(int x)  
37:  {  
38:    //you cannot buy what is already sold  
39:    if(sold[x]) return false;  
40:      
41:    //Shoe is x is now being sold to...  
42:    sold[x] = true;  
43:      
44:    //Let's decide who buys this shoe  
45:    for(int i=0; i < waitList[x].size(); i++)  
46:    {  
47:      int pot = waitList[x][i]; //potential customer id  
48:        
49:      /*  
50:       If the potential customer has not made any purchases,  
51:       or if somebody else can buy what he previously bought  
52:       (there is another person who can buy he has already purchased  
53:       */  
54:      if( purchase[pot] == -1 || buy( purchase[pot] ))  
55:      {  
56:        //the current potential customer takes this shoe  
57:        purchase[pot] = x;  
58:        return true; //successful purchase  
59:      }  
60:    }  
61:    //Every already bought other shoes  
62:    return false;  
63:  }  
64:    
65:  obj shoes[maxN]; //info on shoes  
66:  obj cus[maxN]; //info on customers  
67:  ll cost[maxN];  
68:    
69:  int main()  
70:  {  
71:    ios_base::sync_with_stdio(false);  
72:    int N,M;  
73:    cin>>N;  
74:    for(int i=0; i < N; i++)  
75:    {  
76:      cin>>shoes[i].cost>>shoes[i].size;  
77:      shoes[i].id = i;  
78:      cost[i] = shoes[i].cost;  
79:    }  
80:    
81:    sort(shoes, shoes+N, compSize);  
82:      
83:    cin>>M;  
84:    for(int i=0; i < M; i++)  
85:    {  
86:      cin>>cus[i].cost>>cus[i].size;  
87:      cus[i].id = i;  
88:    }  
89:      
90:    for(int i=0; i < M; i++)  
91:    {  
92:      int idx = int(lower_bound(shoes, shoes+N, obj(-1,cus[i].size,-1), compSize) - shoes);  
93:        
94:      while( shoes[idx].size == cus[i].size || shoes[idx].size == (cus[i].size + 1) )  
95:      {  
96:        if(shoes[idx].cost <= cus[i].cost)  
97:          waitList[shoes[idx].id].push_back( cus[i].id );  
98:        idx++;  
99:      }  
100:    }  
101:      
102:    sort(shoes, shoes + N, compCost);  
103:      
104:    memset(purchase, -1, sizeof(purchase));  
105:      
106:    for(int i=0; i < N; i++)  
107:    {  
108:      memset(sold, false, sizeof(sold));  
109:      buy( shoes[i].id );  
110:    }  
111:      
112:    ll total = 0LL;  
113:    vector< pair<int,int> >ans;  
114:      
115:    for(int i=0; i < M; i++)  
116:    {  
117:      if(purchase[i] == -1)  
118:        continue;  
119:        
120:      total += cost[ purchase[i] ];  
121:      ans.push_back( make_pair( i+1, purchase[i] + 1) );  
122:    }  
123:      
124:    cout<<total<<endl;  
125:    cout<<ans.size()<<endl;  
126:      
127:    for(int i=0; i < ans.size(); i++)  
128:      cout<<ans[i].first<<" "<<ans[i].second<<endl;  
129:      
130:    return 0;  
131:  }  


E - Tetrahedron
Approach
We need to find how many ways an ant can go from D to D without staying at one vertice for more than one turn.

 Fix first and last vertice as D.
 There are 4 vertex : A,B,C and D
 Then at each step, we have 3 vertex (our ant cannot stay at the current vertice) to move to.
 So there are 3^(N-2) possible routes from first vertex to last vertex.

 Note that at the (last - 1) step, the vertice cannot be D because our ant cannot stay at D.
 So we need to eliminate cases where (last-1) step ends at vertice D.

 Let ans(i) = answer to the question when (n == i).
 Then ans(i) = 3^(N-2) - ans(i-1)

Source Code

1:  int mod = 1000000007ll;  
2:    
3:  int main() {  
4:       int N;  
5:       cin >> N;  
6:    
7:       ll ans = 0;  
8:       if (N == 1) {  
9:            cout << 0;  
10:            return 0;  
11:       }  
12:      
13:       ll threes = 1;  
14:       ll prev = 0;  
15:       ll cur = 0;  
16:       for (int i = 2; i <= N; i++) {  
17:            threes = 3ll*threes;  
18:            cur = threes - prev;  
19:            if (cur < 0) cur +=mod;  
20:            threes %= mod;  
21:            cur %= mod;  
22:            prev = cur;  
23:       }  
24:         
25:       cout << cur;  
26:       return 0;  
27:  }  

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: