PROBLEM LINK:Author: Sergey Nagin DIFFICULTY:Easy-Medium PREREQUISITES:maths, probability PROBLEM:Sereja is playing a game with his friends. There are a total of $N$ players where everyone is numbered from $1$ to $N$. Sereja chose number $1$ for himself. QUICK EXPLANATION:Define $P_{i,\textrm{win}}$ as the probability of $i^{th}$ player winning a single turn(i.e. a game of $M$ rounds) and $P_{\textrm{draw}}$ as the probability that no players wins a single turn of the game. The probability of Sereja ever winning the game can be define as sum of Geometric Progression series $\prod_{i=0}^{\inf} (P_\textrm{draw})^i * P_{1,\textrm{win}}$ which can be written as $\frac{P_{1, \textrm{win}}}{1- P_{\textrm{draw}}}$. where, $P_{i,\textrm{win}} = \prod_{j=1}^{M} P_{i,j}$ and $P_\textrm{draw} = 1 - \sum_{i=1}^{N}P_{i,\textrm{win}}$. To take care of precision represent all intermediate values in form of $x*10^{-y}$, where $x \ge 0$. ================ EXPLANATION:================ So, let's see how we calculate $Pr_{i, 1}$. What's the probability that that player $i$ wins the first turn? Winning a game means winning all the $M$ rounds. So probability of winning is product of probabilities of winning all individual rounds because all rounds are independent as said in statement. So, the probability of $i^{th}$ player winning first turn is $\prod_{j=1}^{M} P_{i, j}$. Let's call this probability $P_{i, \textrm{win}}$. Now, interesting thing to note is that this probability of winning a turn for any player $i$ will remain constant always. Now, let's see what's the probability of Sereja winning the game in second turn. The probability is product of nobody winning in the first turn and Sereja winning in second turn. First let's see what's the probability of nobody winning the first turn. It's $P_{\textrm{draw}} = 1 - \sum_{i=1}^{N} P_{i, \textrm{win}}$ because each player's winning chances are independent. So, $Pr_{1, 2}$ can be written as $P_{\textrm{draw}} * P_{1, \textrm{win}}$. Similarly, probability of Sereja winning in third turn is product of probability of draw in first two turns and Sereja winning the third turn. So, we can write probability of Sereja winning ever as $\sum_{i = 0}^{\inf} (P_{\textrm{draw}})^{i} * P_{1, \textrm{win}}$. Now, we notice that it's a Geometric Progression. Sum of $\sum_{i=0}^{\inf} r^{i}*a$ can be written as $\frac{a}{1-r}$ if $r \lt 1$, which is true in this case since its a probability. So, our sum of infinite series is $\frac{P_{1, \textrm{win}}}{1- P_{\textrm{draw}}}$ which is basically $\frac{P_{1, \textrm{win}}}{\sum_{i=1}^{N}P_{i,\textrm{win}}}$. A corner case here would be if $P_{1, \textrm{win}}$ is $0$, then our answer is $0$. Now, let's see what issues can occur in the implementation. Even $\textrm{long double}$ data type can store information upto $18$ digits with precision. But, we are going to take product of probabilities for each player, that means for any $i$, $P_{i,\textrm{win}}$ could be upto $10^{-N}$, surely we can't store data with such precision. But, interesting observation would be that our numerator and denominator both could be upto $10^{-N}$, so why not take the powers of $10$ common? So, while calculating $P_{i,\textrm{win}}$, at each step we will store the most significant digits of our answer in the form $x*10^{-y}$. And then while preforming division, we'll subtract the powers of $10$ of the denominator. The following pseudo code will make the method clear: long double arr[M]; int powers[M]; cin >> n >> m; for(int i = 0; i < n; i++){ long double pIWin = 1; //probability i'th player wins a turn int powTen = 0; //store in the form pIWin*10^(-powTen) for(int j = 0; j < m; j++){ long double pIJ; cin >> pIJ; //makes pIJ >= 0 pIJ *= 1e4; //increasing negative power of ten by 4 powTen += 4; //multiplying with pIWin //now since we are multiplying pIWin with numbers > 1 //it might exceed 10^10000 soon //so we store only 4 most significant digits pIWin *= pIJ; if(pIWin>1e4){ pIWin /= 1e4; powTen -= 4; } } //store both values in an array //which we use later to calculate denominator and numerator arr[i] = pIWin; powers[i] = powTen; } //numerator and denominator long double num=arr[0],den; for(int i = 0; i < n; i++){ //pIWin is arr[i]*10^(-powers[i]) //whereas our numerator is at index 0 //so, we bring power of numberator down to the denominator //actual value of pIWin can be written as arr[i]*10^(powers[0]-powers[i]) //if numerator's value is arr[0] den += arr[i]*pow(10,((ten[0]-ten[i]))); } if(abs(num)<1e-9)printf("%.9Lf\n",(long double)0); else printf("%.9Lf\n",(num/den)); COMPLEXITY:Complexity is $O(N*M)$. ALTERNATE METHOD FOR PRECISION:You can convert probabilities to integers and for multiplying them you need to multiply a large number with a small number at each step which can be done quickly using methods such as by using divide and conquer and using a fast multiplication algorithm when combining the results from the two halves of the divide and conquer algorithm - a fast multiplication algorithm could be something like Karatsuba or maybe something based on FFT, but it would be an overkill in my opinion. AUTHOR'S, TESTER'S SOLUTIONS:
This question is marked "community wiki".
asked 29 Jun '15, 20:37 ![]()
|
I've a less complex solution for this problem: answered 13 Jul '15, 15:42 ![]()
Please explain your solution
(13 Jul '15, 16:44)
4
Wow! this is a really good solution! He has just played around with the formula and calculated Ai / A1 on the fly without actually calculating the whole Ai first. Ai is the product of numbers in the ith row (probability that ith guy wins). The answer initially is A1 / (A1 + A2 + ... + An). Divide both sides by A1 and you get 1 / (1 + A2/A1 + A3/A1 + ... + An/A1). nice!
(13 Jul '15, 20:15)
oh. nice soln. he nailed it :)
(13 Jul '15, 20:58)
Yeah, @rushilpaul explained it very correctly. Thanks :-) The variable 'waah' takes care of when the probability is zero
(13 Jul '15, 21:25)
|
The test cases were weak ; the following solution gets 100 points : http://www.codechef.com/viewsolution/7474964 All I did was scale all the elements by 25. answered 13 Jul '15, 16:30 ![]()
1
...I HAD TO USE MY OWN
(13 Jul '15, 17:36)
2
+1 for the variable names.
(14 Jul '15, 02:24)
|
I used BigInteger in Java, but got WA on the large input set.. Unable to understand why.. Is there a way to look at 1 sample test from large input set ? === Ok I understand my mistake. I share it with you, because it's very stupid and not easy to find. I read the p[i][j] as doubles, then multiply them by 10000, then converting them to Integer. This is were the bug was. Because of the following: if double x=0.0710 then x*10000=709.99999999 and when casting to Int, this gives 709...(!!!) This is a lesson. Never cast a double to an int basically.. answered 13 Jul '15, 15:36 ![]()
|
Got AC with big integer calculation without any fast multiplication optimization. The only trick I used is when you divide big integer on another big integer the only precision you need is first 6 digits after decimal point. So it is enough to divide up to 9th digit and the stop dividing. answered 13 Jul '15, 16:45 ![]()
Your solution is similar to my solution because we both multiplied by scalars instead of using a
(13 Jul '15, 17:52)
|
i had the same logic...just messed up with the precision thing !!...nice problem though!!! answered 13 Jul '15, 15:28 ![]()
|
exactly the same logic.....just precision errors..... Also, the first test case was weak though, even my guess solution which i first applied for finding minimum of every row and printing the minimum of first row passed. This should not have happened.... Still a good question. Came to know about the importance of precision of numbers and how to take care of it. answered 13 Jul '15, 15:43 ![]()
|
I used the BigDecimal class in java and got it accepted. Here's the link to my solution http://www.codechef.com/viewsolution/7465863 . answered 13 Jul '15, 15:44 ![]()
How did your solution pass? It went over the time limit of 3 seconds!
(13 Jul '15, 17:47)
Time limit for java is not 3 seconds. It is for C/C++, I guess. JAVA is slow.
(13 Jul '15, 19:30)
|
I have used the BigDecimal class in java. Here is the link to my solution. http://www.codechef.com/viewsolution/7465863 answered 13 Jul '15, 15:44 ![]()
|
This problem really pissed me off. I was very confident of my solution but I kept getting WA's. I still don't know the reason though. My maths is the same as author's but my answer may be more precise because I am never rounding off (PYTH). I opened up a discussion for this. It will be helpful if someone provides any guidance. answered 13 Jul '15, 17:08 ![]()
|
I don't understand why the author suggests a "fast multiplication method like Karatsuba or FFT" for multiplying In this solution, I simply find the product of all of the probabilities by having one answered 13 Jul '15, 17:44
|
@amaneureka Nice idea, but the following test case should not work: answered 14 Jul '15, 01:47 ![]()
@amaneureka: Sorry, I didn't mean to not let you sleep because of my comment. If you have time, could you swap the 3rd and the 4th 'for' loop? The idea of my test case was that (0.0001/0.9999)^5000 would be 0 because of the precision and that multiplying back by (0.9999/0.0001)^5000 cannot change that. (For example http://paste.ideaslabs.com/show/CvvvJDHinq returns 0 and 0 in Java).
(14 Jul '15, 12:40)
|
@spagiarri Here :-) answered 14 Jul '15, 04:14 ![]()
|