1

So, I was solving this problem: http://www.spoj.com/problems/IMMERSED/

A fantastic discovery is about to happen in the biology field, and you are part of the team making the research. The research is about measuring the cells growth in an environment oxygenless and in presence of a toxic substance. The team came to a courious hypothesis, the analyzed data tells them that: the growth, the number of days and the toxicity; are related by this formula:

P = N*NcN;

where P is the growth measured in thousands of cells.

N is the elapsed number of days.

and c is the constant that relates to the toxicity level of the experiment.

Your biology partners need to takeout some tissues from the cells when these cells reach a specific growth. They require you to write a program that tells them the exact time when this will happen, given a toxicity level and the growth required.

Input

The first line is T (1 ≤ T ≤ 40,000), the number of test cases, then T test cases follow.

Each test case is a line with 2 integers(P c) separated by a space.

P (1 ≤ P ≤ 1015)

c (1 ≤ c ≤ 5)

Output

For each test case you have to output the expected time in decimal format.

What I did was used binary search for finding the number of days as follows:

#define eps 1e-7
const double cont = 14.0;
double p,c;
double binary (double start, double end);
double func (double n);
int main (void)
{
    int t;
    cin>>t;
    while (t != 0)
    {
        cin>>p>>c;
        double mid,ans,start=0,end=cont;
        ans = binary(start,end);
        cout.precision(6);
        cout<<fixed<<ans<<"\n";         
        t--;
    }
    return 0;
}

double func (double n)
{
    double ret = n*pow(n,c*n);
    return ret;
}

double binary (double start, double end)
{
    if (start <= end)
    {
        double mid = start + (end-start)/2.0;
        if (func(mid)-p <= eps)
            return mid;
        else if (func(mid)-p > eps)
            return binary(start,mid);
        else
            return binary(mid,end);
    }
}

However, on running my code, it gives incorrect answer on even the given test cases which are:

Input:
3
1 1
3 4
100 1
Output:
1.000000
1.207384
3.086308

My output (for the above input)

0.875
0.875
1.75

PS: I didn't post the libraries and all to avoid cluttering. Also, I will set it to be 6 decimal places once I get the correct value. I just want to know, is my logic incorrect or my binary search has been implemented incorrectly?

Edit: New code I submitted

double binary (double start, double end)
    {
        if (start <= end)
        {
            double mid = start + (end-start)/2.0;
            double delta = func(mid) - p;
            if (delta < -1*eps)
                return binary(mid,end);
            else if (delta > eps)
                return binary(start,mid);
            else
                return mid;
        }
    }
hans
  • 147
  • 2
  • 8
  • 1
    This if statement looks suspicious; `if (func(mid)-p <= eps)`. If they can be equal, you must test that in a [different way](http://stackoverflow.com/q/588004/33499). Also I don't understand the reason of the last `else`. – wimh Nov 01 '15 at 10:39
  • Your mid is totally dependent on start and end values and that is what you are returning. How do u expect it to be 1 given the fact that you are starting from 1 to 14 and than dividing it into halves...so 0--14, 0 to 7, 0 to 3.5 , 0 to 1.75... 0 to .875 ..Are you sure mid is what you want? – Megha Nov 01 '15 at 10:47
  • @Meghaa, but in binary search, we always find out the mid value and compare it with that. Should it be different in this case? – hans Nov 01 '15 at 10:48
  • well i think its not about binary search...its about what you are expecting your code to do...do u wish to find out the day at which a certain calculation was less than an expected value? – Megha Nov 01 '15 at 10:53
  • If I submit your code as C++ (g++ 5.1) code, I get Wrong Answer. Can you please post the exact code you're submitting and how? – IVlad Nov 01 '15 at 15:18
  • Even if you compute to 6 places, your cout doesn't display to 6 places. You need to adjust the precision of cout. http://www.cplusplus.com/reference/ios/ios_base/precision/ – JSF Nov 01 '15 at 15:22
  • I have done that already. See my code in main now. – hans Nov 01 '15 at 16:22
  • precision in iostream does not have the same meaning as in that online test. `cout.precision(6)` is six places total, but the contest asks for six places to the right of the `.` plus one or two places to the left. – JSF Nov 01 '15 at 16:29

2 Answers2

4

You are testing in an unsound sequence:

if (func(mid)-p <= eps)
    return mid;
else if (func(mid)-p > eps)
    return binary(start,mid);
else
    return binary(mid,end);

Try

if (func(mid)-p < -eps)
    return binary(mid,end);
else if (func(mid)-p > eps)
    return binary(start,mid);
else
    return mid;

I'm also not sure of the two recursive calls. I kept your logic for which is called in which case (because I might have misunderstood your formula) but they look backwards to me.

What I'm sure of is that you should test for (and use a recursive calls for) the two outside cases (func(mid)-p < -eps and func(mid)-p > eps) before the inside case (which is then effectively abs(func(mid)-p) < eps)

Edit: The cleaner (faster) version of that binary search is:

double binary (double start, double end)
{
    for (;;)
    {
        double mid = (start + end) * .5;
        double delta=func(mid)-p;

        if ( delta < -eps)
            start = mid;
        else if (delta > eps)
            end = mid;
        else
            return mid;
    }
}

A Newton search is likely faster than that.

JSF
  • 5,281
  • 1
  • 13
  • 20
  • Well, this works but now, with this logic, the problem gives Time Limit Exceeded. I don't know if we can do better than binary search. Can we? – hans Nov 01 '15 at 12:02
  • 1
    @hans online judge compilers might not use optimizations. Try storing the value of `func(mid)` in a variable and using that variable in your if and else if. – IVlad Nov 01 '15 at 12:08
  • Trivially you can get faster by computing and storing func(mid) once per step instead of twice. You can also make it faster using a loop instead of recursion. (Though an optimized build ought to do that for you in the obvious case of tail recursion). For most well behaved functions, a Newton search works better than binary, but you need some calculus skill (or a visit to http://www.wolframalpha.com/ to code that). – JSF Nov 01 '15 at 12:10
  • 1
    wolframalpha gave `(dP(C, N))/(dN) = N^(C*N)*(1+C*N*(1+log(N)))` as the answer to the calculus question that you would need in order to code a Newton search. You still would need to know what a Newton search is. – JSF Nov 01 '15 at 12:18
  • In an optimized build, using globals for p, c, and eps during the search is slower than passing them along as function parameters. But in an unoptimized build, globals are faster than function parameters. – JSF Nov 01 '15 at 12:39
  • You're calculating way too accurately, there's no complicated optimization necessary. You're getting func(N) to be accurate within 1e-7, when you only need N within 1e-6, and the function is very quickly increasing, so getting func(N) exactly is much harder. – MPeti Nov 01 '15 at 13:24
  • @MPeti, you are correct about the accuracy, but incorrect about its impact on overall performance. Saving a few iterations off the end of the search makes a small difference compared to the other possible optimizations. – JSF Nov 01 '15 at 14:35
  • @JSF, I did this (See my original post) and it still gives TLE. (I also added the optimisation suggested by Mpetl but no improvement). I copied the exact same code you posted but still I got TLE. – hans Nov 01 '15 at 14:52
  • Out of curiosity, I did a quick test with Newton. Maybe I coded something wrong, or maybe the function is tricky for Newton. From any point slightly above the correct answer Newton converges much faster than binary search. From significantly above the correct answer Newton converges slowly. From below the correct answer Newton blows up. Assuming that wasn't the result of a bug, the only practical way to use Newton would be binary search until slightly above the final answer, then switch to Newton. – JSF Nov 01 '15 at 15:08
  • 1
    @Mpeti, I was wrong in my reply to you. I had only tested the cases hans gave. Over the full range, there is a **much** better reason to follow your suggestion (and that might be the major remaining problem). When P is very large, func(N) can't be computed accurately enough using the `double` type, so the loop never stops, even though `mid` is the correct value for `N` – JSF Nov 01 '15 at 15:15
  • If you take the log of both sides of the defining equation before doing the calculus to define the Newton method, then Newton is extremely fast and well behaved over the solution space and blows the doors off a binary search. – JSF Nov 01 '15 at 16:00
  • I registered for that site and submitted a simple version using Newton search (after restating the original problem taking ln of each side). It says that took .31 seconds, which is most of the time limit. A binary search would be much slower, so I think you can't make a binary search fast enough. – JSF Nov 01 '15 at 16:24
2

Beside the problem with the search JSF explained, you're also calculating way more accurately than you need to.

You need N with an accuracy of 10^-6. With your implementation, you're searching until you find the value where you have P with an accuracy of 10^-7. Since the function is exponential and therefore very steep, i.e. P changes very quickly with N, that's going to result in much more computation than necessary.

Instead, you should have a stopping condition end - start < 1e-6.

Now here I have a semantic issue: Does an accuracy of 10^-6 mean that all the digits have to be correct, or is the last one allowed to be off by 1? If it's allowed, either start or end will be a good answer after stopping. If not, you could round them upwards to the nearest 10^-6. You should round up because the statement is probably asking for the first N where func(N) > P, not an approximation for equality. Then if those are different, check if the rounded value of start satisfies the statement. If yes, output that, if not, output the rounded value of end.

Given the upper bound of 14.0, the search space is just 14.0 / 1e-6 = 1.4 * 10^8 elements. The base 2 log of that is just over 27, so you should only need 28 iterations for each test case (maybe 27 if you calculate a more accurate upper bound). This definitely shouldn't require complicated optimizations to pass.

MPeti
  • 621
  • 4
  • 9
  • that doesn't really help to remove the TLE though. :/ – hans Nov 01 '15 at 14:59
  • @hans, post the version you used when you tried MPeti's suggestion. Maybe you misunderstood that suggestion and did not properly deal with the case of P very large. Also test the case of P very large before submitting your proposed solution. – JSF Nov 01 '15 at 15:17