C++
// This program use radian mode
//#define DEBUG
#ifdef DEBUG
#define _GLIBCXX_DEBUG
#include <cassert>
#else
#define assert(x) void(0)
#endif
namespace std {
/// Used for un-debug.
struct not_cerr_t {
} not_cerr;
}
template <typename T>
std::not_cerr_t& operator<<(std::not_cerr_t& not_cerr, T) {return not_cerr;}
#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>
#include <array>
#include <bitset>
#include <string>
#include <sstream>
#ifndef DEBUG
#define cerr not_cerr
#endif // DEBUG
// String conversion functions, because of some issues with MinGW
template <typename T>
T from_string(std::string st) {
std::stringstream sst (st);
T result;
sst >> result;
return result;
}
template <typename T>
std::string to_string(T x) {
std::stringstream sst;
sst << x;
return sst.str();
}
template <typename T> int sgn(T val) {
return (T(0) < val) - (val < T(0));
}
const int N_ITER = 1000, N_DIGIT = 5, NSOL = 4;
std::array<int, N_DIGIT> digits;
double target;
typedef std::bitset<N_ITER> stfunc; // sin-tan expression
// where sin = 0, tan = 1
double eval(const stfunc& fn, int length, double value) {
while (length --> 0) {
value = fn[length] ? std::tan(value) : std::sin(value);
}
return value;
}
struct stexpr { // just a stfunc with some information
double x = 0, val = 0; // fn<length>(x) == val
int length = 0;
stfunc fn {};
// bool operator[] (const int x) {return fn[x];}
double eval() {return val = ::eval(fn, length, x);}
};
struct expr { // general form of stexpr
// note that expr must be *always* atomic.
double val = 0;
std::string expr {};
void clear() {
val = 0;
expr.clear();
}
// cos(cos(x)) is in approx 0.5 - 1,
// so we can expect that sin(x) and tan(x) behaves reasonably nice
private: void wrapcos2() {
expr = "(cos_2 " + expr + ")"; // we assume that all expr is atomic
val = std::cos(std::cos(val));
}
public: void wrap1() {
if (val == 0) {
expr = "(cos " + expr + ")"; // we assume that all expr is atomic
val = std::cos(val);
}
if (val == 1) return;
wrapcos2(); // range 0.54 - 1
int cnt_sqrt = 0;
for (int i = 0; i < 100; ++i) {
++cnt_sqrt;
val = std::sqrt(val);
if (val == 1) break;
}
expr = "(sqrt_" + to_string(cnt_sqrt) + " " + expr + ")"; // expr must be atomic
}
};
stexpr nearest(double initial, double target) {
stexpr result; // built on the fn of that
result.x = initial;
double value [N_ITER + 1];
value[0] = initial;
for (result.length = 1; result.length <= N_ITER; ++result.length) {
double x = value[result.length-1];
if (x < target) {
result.fn[result.length-1] = 1;
} else if (x > target) {
result.fn[result.length-1] = 0;
} else { // unlikely
--result.length;
// result.val = x;
result.eval();
assert(result.val == x);
return result;
}
value[result.length] = result.eval(); // this line takes most of the time
if (value[result.length] == value[result.length-1])
break;
}
// for (int i = 0; i < N_ITER; ++i) {
// std::cerr << i << '\t' << value[i] << '\t' << (value[i] - target) << '\n';
// }
double mindiff = std::numeric_limits<double>::max();
int resultlength = -1;
result.length = std::min(N_ITER, result.length);
for (int l = 0; l <= result.length; ++l) {
if (std::abs(value[l] - target) < mindiff) {
mindiff = std::abs(value[l] - target);
resultlength = l;
}
}
result.length = resultlength;
double val = value[resultlength];
assert(std::abs(val - target) == mindiff);
if (val != target) { // second-order optimization
for (int i = 1; i < result.length; ++i) {
// consider pair (i-1, i)
if (result.fn[i-1] == result.fn[i]) continue; // look for (sin tan) or (tan sin)
if (val < target && result.fn[i-1] == 0) { // we need to increase val : sin tan -> tan sin
result.fn[i-1] = 1;
result.fn[i] = 0;
double newvalue = result.eval();
// if (!(newvalue >= val)) std::cerr << "Floating point sin-tan error 1\n";
if (std::abs(newvalue - target) < std::abs(val - target)) {
// std::cerr << "diff improved from " << std::abs(val - target) << " to " << std::abs(newvalue - target) << '\n';
val = newvalue;
} else {
result.fn[i-1] = 0;
result.fn[i] = 1; // restore
#ifdef DEBUG
result.eval();
assert(val == result.val);
#endif // DEBUG
}
} else if (val > target && result.fn[i-1] == 1) {
result.fn[i-1] = 0;
result.fn[i] = 1;
double newvalue = result.eval();
// if (!(newvalue <= val)) std::cerr << "Floating point sin-tan error 2\n";
if (std::abs(newvalue - target) < std::abs(val - target)) {
// std::cerr << "diff improved from " << std::abs(val - target) << " to " << std::abs(newvalue - target) << '\n';
val = newvalue;
} else {
result.fn[i-1] = 1;
result.fn[i] = 0; // restore
#ifdef DEBUG
result.eval();
assert(val == result.val);
#endif // DEBUG
}
}
}
}
double newdiff = std::abs(val - target);
if (newdiff < mindiff) {
mindiff = std::abs(val - target);
std::cerr << "ok\n";
} else if (newdiff > mindiff) {
std::cerr << "Program error : error value = " << (newdiff - mindiff) << " (should be <= 0 if correct) \n";
std::cerr << "mindiff = " << mindiff << ", newdiff = " << newdiff << '\n';
}
result.eval(); // set result.result
assert(val == result.val);
return result;
}
expr nearest(const expr& in, double target) {
stexpr tmp = nearest(in.val, target);
expr result;
for (int i = 0; i < tmp.length; ++i)
result.expr.append(tmp.fn[i] ? "tan " : "sin ");
result.expr = "(" + result.expr + in.expr + ")";
result.val = tmp.val;
return result;
}
int main() {
double totalscore = 0;
assert (std::numeric_limits<double>::is_iec559);
std::cerr << std::setprecision(23);
// double initial = 0.61575952241185627;
// target = 0.6157595200093855;
// stexpr a = nearest(initial, target);
// std::cerr << a.val << ' ' << a.length << '\n';
// return 0;
while (std::cin >> digits[0]) {
for (unsigned i = 1; i < digits.size(); ++i) std::cin >> digits[i];
std::cin >> target;
/* std::string e;
// int sum = 0;
// for (int i : digits) {
// sum += i;
// e.append(to_string(i)).push_back('+');
// }
// e.pop_back(); // remove the last '+'
// e = "cos cos (" + e + ")";
// double val = std::cos(std::cos((double)sum));
//
// stexpr result = nearest(val, target); // cos(cos(x)) is in approx 0.5 - 1,
// // so we can expect that sin(x) and tan(x) behaves reasonably nice
// std::string fns;
// for (int i = 0; i < result.length; ++i) fns.append(result.fn[i] ? "tan" : "sin").push_back(' ');
//
// std::cout << (fns + e) << '\n';
// continue;*/
std::array<expr, NSOL> sols;
expr a, b, c, d; // temporary for solutions
/* ----------------------------------------
solution 1 : nearest cos cos sum(digits) */
a.clear();
for (int i : digits) {
a.val += i; // no floating-point error here
a.expr.append(to_string(i)).push_back('+');
}
a.expr.pop_back(); // remove the last '+'
a.expr = "(" + a.expr + ")";
a.wrap1();
sols[0] = nearest(a, target);
/* -----------------------------------------
solution 2 : a * tan(b) + c (also important) */
// find b first, then a, then finally c
a.clear(); b.clear(); c.clear(); // e = a, b = e1, c = e2
a.expr = to_string(digits[0]);
a.val = digits[0];
a.wrap1();
b.expr = "(" + to_string(digits[1]) + "+" + to_string(digits[2]) + ")";
b.val = digits[1] + digits[2];
b.wrap1();
c.expr = to_string(digits[3]);
c.val = digits[3];
c.wrap1();
d.expr = to_string(digits[4]);
d.val = digits[4];
d.wrap1();
b = nearest(b, std::atan(target));
double targetA = target / std::tan(b.val);
int cnt = 0;
while (targetA < 1 && targetA > 0.9) {
++cnt;
targetA = targetA * targetA;
}
a = nearest(a, targetA);
while (cnt --> 0) {
a.val = std::sqrt(a.val);
a.expr = "sqrt " + a.expr;
}
a.expr = "(" + a.expr + ")"; // handle number of the form 0.9999999999
/// partition of any number to easy-to-calculate sum of 2 numbers
{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}
double targetC, targetD; // near 1, not in [0.9, 1), >= 0.1
// that is, [0.1, 0.9), [1, inf)
double target1 = target - (a.val * std::tan(b.val));
double ac = std::abs(target1), sc = sgn(target1);
if (ac < .1) targetC = 1 + ac, targetD = -1;
else if (ac < 1) targetC = 1 + ac/2, targetD = ac/2 - 1;
else if (ac < 1.8 || ac > 2) targetC = targetD = ac/2;
else targetC = .8, targetD = ac - .8;
targetC *= sc; targetD *= sc;
c = nearest(c, std::abs(targetC)); if (targetC < 0) c.val = -c.val, c.expr = "(-" + c.expr + ")";
d = nearest(d, std::abs(targetD)); if (targetD < 0) d.val = -d.val, d.expr = "(-" + d.expr + ")";
sols[1].expr = a.expr + "*tan " + b.expr + "+" + c.expr + "+" + d.expr;
sols[1].val = a.val * std::tan(b.val) + c.val + d.val;
std::cerr
<< "\n---Method 2---"
<< "\na = " << a.val
<< "\ntarget a = " << targetA
<< "\nb = " << b.val
<< "\ntan b = " << std::tan(b.val)
<< "\nc = " << c.val
<< "\ntarget c = " << targetC
<< "\nd = " << d.val
<< "\ntarget d = " << targetD
<< "\n";
/* -----------------------------------------
solution 3 : (b + c) */
target1 = target / 2;
b.clear(); c.clear();
for (int i = 0; i < N_DIGIT; ++i) {
expr &ex = (i < 2 ? b : c);
ex.val += digits[i];
ex.expr.append(to_string(digits[i])).push_back('+');
}
b.expr.pop_back();
b.expr = "(" + b.expr + ")";
b.wrap1();
c.expr.pop_back();
c.expr = "(" + c.expr + ")";
c.wrap1();
b = nearest(b, target1);
c = nearest(c, target - target1); // approx. target / 2
sols[2].expr = "(" + b.expr + "+" + c.expr + ")";
sols[2].val = b.val + c.val;
/* -----------------------------------------
solution 4 : a (*|/) (b - c) (important) */
a.clear(); b.clear(); c.clear(); // a = a, b = e1, c = e2
a.expr = to_string(digits[0]);
a.val = digits[0];
a.wrap1();
b.expr = "(" + to_string(digits[1]) + "+" + to_string(digits[2]) + ")";
b.val = digits[1] + digits[2];
b.wrap1();
c.expr = "(" + to_string(digits[3]) + "+" + to_string(digits[4]) + ")";
c.val = digits[3] + digits[4];
c.wrap1();
// (b-c) should be minimized
bool multiply = target < a.val;
double factor = multiply ? target / a.val : a.val / target;
target1 = 1 + 2 * factor; // 1 + 2 * factor and 1 + factor
std::cerr << "* Method 4 :\n";
std::cerr << "b initial = " << b.val << ", target = " << target1 << ", ";
b = nearest(b, target1);
std::cerr << " get " << b.val << '\n';
std::cerr << "c initial = " << c.val << ", target = " << b.val - factor << ", ";
c = nearest(c, b.val - factor); // factor ~= e1.val - e2.val
std::cerr << " get " << c.val << '\n';
sols[3].expr = "(" + a.expr + (multiply ? "*(" : "/(") +
( b.expr + "-" + c.expr )
+ "))";
factor = b.val - c.val;
sols[3].val = multiply ? a.val * factor : a.val / factor;
std::cerr << "a.val = " << a.val << '\n';
/* ----------------------------------
Final result */
int minindex = 0;
assert(NSOL != 0);
for (int i = 0; i < NSOL; ++i) {
if (std::abs(target - sols[i].val) < std::abs(target - sols[minindex].val)) minindex = i;
std::cerr << "Sol " << i << ", diff = " << std::abs(target - sols[i].val) << "\n";
}
std::cerr << "Choose " << minindex << "; target = " << target << '\n';
totalscore += std::abs(target - sols[minindex].val);
std::cout << sols[minindex].expr << '\n';
}
// #undef cerr // in case no-debug
std::cerr << "total score = " << totalscore << '\n';
}
Input from standard input, output to standard output.
Is there any specific reason the trigonometric functions have to use degrees? Could an option possibly be added for the answer to specify either radians or degrees? – notjagan – 2017-08-20T23:16:08.873
Does the set of digits contain necessarily a non zero digit? – mdahmoune – 2017-08-21T15:13:04.150
@mdahmoune The test cases are randomly generated, so the digits could be all 0. You'd just have to do your best in that situation. In degree mode I was able to get all the way up to 3283.14 with
cos(0)/sin(0^0)/sin(0^0)
. – geokavel – 2017-08-21T15:28:43.870Thanx for your complete answer :) – mdahmoune – 2017-08-21T16:12:58.217
Is it the same scoring method for the 5 different sections? Abs(target_value-generated_expression_value)? I – mdahmoune – 2017-08-22T11:17:46.350
@mda It is, but you have to remember that the number of points you get is based on how your program scores compared to the other programs. – geokavel – 2017-08-22T13:49:24.590
@geokavel The probability of at least 1 test case is all zero is actually 2‰. We can make the output arbitrarily long such that it output in time, correct? – user202729 – 2017-08-23T05:26:52.410
@user202729 Do you mean outputting all the answers at the very end? Yeah, that works. – geokavel – 2017-08-23T14:43:10.660
Is the concatenation considered as an operation? I means, for example for
8 2 4 7 2 : 65
can I produce65
by concatenating the result of(8+2-4)=6
and(7-2)=5
? – mdahmoune – 2017-08-23T15:26:30.080@mda No, it's not allowed. You can use program 1 to test out what's allowed. – geokavel – 2017-08-23T15:29:09.750
@geokavel With the new evaluator the time limit is
evaluate + compute <= 1 min
orevaluate <= 1 min, compute <= 1 min
? – user202729 – 2017-08-25T08:09:53.820@geokavel So each test case is a round (independent of each other?) – user202729 – 2017-08-25T14:23:52.410
@user202729 Sorry, I think I mis-answered one of your previous questions. The 60s time limit is just for producing the outputs, not evaluating them. – geokavel – 2017-08-25T14:50:08.067
Is it possible to give the number of cores in your test cpu machine? – mdahmoune – 2017-08-28T10:04:47.360
@mdahmoune Yeah, two. – geokavel – 2017-08-28T14:57:02.200
@mdahmoune , geokavel Unfortunately, despite the bounty, no one had a better answer than mine yet. Anyone consider writing one? – user202729 – 2017-08-29T16:06:52.263
@user202729, geokavel Yes, I'm working on a second draft version.. – mdahmoune – 2017-08-29T16:09:07.690
Perhaps merge the two solutions :D – mdahmoune – 2017-08-29T21:11:51.733
@user202729 feel free to use my function's solution to improve yours – mdahmoune – 2017-08-29T21:25:16.903
@mdahmoune I think using other's idea is cheating. – user202729 – 2017-08-30T05:46:17.820
I reimplemented the evaluator here (TIO link) in C++, but it gives different result for that test case to your Java evaluator. So my score may be worse if the Java evaluator is used. Even on different computers the C++ evaluator return different results.
– user202729 – 2017-09-01T01:59:10.910@user202729 On the TIO version of the Java evaluator I get 0.999999 for that input. However, on the version of the Java evaluator on my computer I get almost the same answer as your TIO C++ link. So I think you're good. – geokavel – 2017-09-01T03:44:19.553
On the TIO Java I get 6.06058941675148E8 , while on TIO C++ I get 606058696.99925720691681, which have difference of about 1000. How can you get 0.999999? (remember to use rad) Because now mdahmoune 's score is ~ 1e-4 now the precision is very important. How about rescore both answer with the C++ evaluator? – user202729 – 2017-09-01T04:06:22.473
@user202729 The difference is only 245. Yes I forgot to change to rad. How do I know whether Java or C++ version is more accurate? Especially since C++ evaluator returns different results on different computers. – geokavel – 2017-09-01T04:11:58.307
@user202729 I'll try to run both with C++ evaluator, but I'm not really skilled with C++ so it might take me a little bit. – geokavel – 2017-09-01T04:14:01.153
Who knows? (we can try evaluate both on Mathematica with a lot of digits of precision) Does Java evaluator returns different results on different computers? – user202729 – 2017-09-01T04:15:28.507
@user202729 I get same exact result on TIO as on my computer for Java version. – geokavel – 2017-09-01T04:19:05.047
@user202729 I get 606058289.20654487609863 when I run C++ version on my computer, compiled with clang. – geokavel – 2017-09-01T04:30:22.407
TIO get 606058696.999... and I (gcc) get exactly 606058697. I think I will move to Java for accuracy. – user202729 – 2017-09-01T04:32:45.833