JMK no matter what

Tough Water Level: CERC 2007

Numerical integration and formula parsing. At first, I started implementing Trapezoidal Rule; but it required an insane amount of formula evaluations; so I wrote code to generate parse tree to speed up evaluation. However, in the end, Simpson's Rule saved the day - it requires less than 50 intervals!

But, the amount of effort I threw into this code makes this worth posting anyway.

lang:cpp
#include<iostream>
#include<algorithm>
#include<string>
#include<cmath>
#include<cstdio>
using namespace std;

#define all(x) (x).begin(),(x).end()
#define CLEAR(x,with) memset(x,with,sizeof(x))

// parse tree node
struct expr
{
    /* fields */
    enum { FIXED, VARIABLE, EXPRESSION } type;
    double value;
    char op;
    expr *left, *right;

    /* constructors and destructors */
    expr(double value) : type(FIXED), value(value) {}
    expr(char op, expr* left, expr* right) : type(EXPRESSION), op(op), left(left), right(right)
    {
        // compile time optimization kkkkkkkkkkkkk
        if(left->type == FIXED && right->type == FIXED)
        {
            type = FIXED;
            value = calc(op, left->value, right->value);
            delete left;
            delete right;
        }
    }
    expr() : type(VARIABLE) {}
    ~expr() { if(type == EXPRESSION) { delete left; delete right; } }

    /* member functions */
    double calc(char op, double a, double b)
    {
        if(op == '^') return pow(a, b);
        if(op == '+') return a+b;
        if(op == '-') return a-b;
        if(op == '*') return a*b;
        return a/b;
    }

    double eval(double x)
    {
        if(type == VARIABLE) return x;
        if(type == FIXED) return value;
        return calc(op, left->eval(x), right->eval(x));
    }
};

// compile an expr* from the given string
expr* parse(const string& exp, int left, int right)
{
    int opened = 0;
    // it's an expression?
    for(int i = right; i >= left; --i)
        if(exp[i] == ')') ++opened;
        else if(exp[i] == '(') --opened;
        else if(opened == 0 && (exp[i] == '+' || exp[i] == '-'))
            return new expr(exp[i], parse(exp, left, i-1), parse(exp, i+1, right));
    // term?
    for(int i = right; i >= left; --i)
        if(exp[i] == ')') ++opened;
        else if(exp[i] == '(') --opened;
        else if(opened == 0 && (exp[i] == '*' || exp[i] == '/' || exp[i] == '^'))
            return new expr(exp[i], parse(exp, left, i-1), parse(exp, i+1, right));
    // factor!
    if(exp[left] == 'x') return new expr();
    if(exp[left] == '(') return parse(exp, left+1, right-1);
    return new expr(atof(exp.c_str() + left));
}

// helper for parse() above
expr* parse(const string& exp) { return parse(exp, 0, exp.size()-1); }

double h, b;

// Composite Simpson's Rule
double integrate(double lo, double hi, expr* f)
{
    const int SPLIT = 100;
    double h = (hi - lo) / SPLIT;
    double ret = f->eval(lo) + f->eval(hi);
    for(int i = 1; i < SPLIT; ++i)
    {
        double add = f->eval(lo + h * i);
        ret += (i % 2 ? 4 : 2) * add;
    }
    return ret * h / 3;
}

// returns a pair of <y coordinate of the center, total mass>
pair<double,double> calc(double a, double b, expr* f)
{
    double mass = integrate(a, b, f);

    // f2 = x * f(x)
    expr f2('*', new expr(), f->clone());
    double y = integrate(a, b, &f2) / mass;

    return make_pair(y, mass);
}

pair<double,double> mix(const pair<double,double>& a, const pair<double,double>& b)
{
    double mass = a.second + b.second;
    return make_pair( (a.first * a.second + b.first * b.second) / mass, mass );
}

const double PI = 2.0 * acos(0.0);

int main()
{
    while(cin >> h >> b)
    {
        if(h == 0 && b == 0) break;

        string R, T;
        cin >> R >> T;

        // water area = PI * (R-T)^2. we omit PI
        string wat = "((" + R + ")-(" + T + "))^2";
        // base area = PI*R^2
        string bas = "(" + R + ")^2";
        // wall area = baseArea - waterArea
        string wal = "(" + bas + ")-(" + wat + ")";

        // generate parse trees
        expr* waterArea = parse(wat);
        expr* baseArea = parse(bas);
        expr* wallArea = parse(wal);

        // calc the (unweighted) center of mass, and the mass of 2 parts of the cup
        pair<double,double> base = calc(0, b, baseArea);
        pair<double,double> wall = calc(b, h, wallArea);

        // the cup weighes 2.5 times more than water
        base.second *= 2.5;
        wall.second *= 2.5;

        // the cup as a whole
        pair<double,double> cup = mix(base, wall);

        // binary search on the height of the water
        double lo = b, hi = h;
        for(int iter = 0; iter < 30; ++iter)
        {
            double mid = (lo+hi) / 2;
            pair<double,double> water = calc(b, mid, waterArea);
            pair<double,double> cupAndWater = mix(cup, water);
            // if the top of the water is lower than the center of gravity, we can pour more
            if(mid < cupAndWater.first)
                lo = mid;
            else
                hi = mid;
        }

        // multiply omitted PI
        double amt = integrate(b, hi, waterArea) * PI;
        printf("Pour %.3lf litres / %.3lf cm of water.\n", amt / 1000.0, hi - b);

        delete waterArea;
        delete baseArea;
        delete wallArea;

    }
}
2009-05-13 13:54:20 | JM | /logs/ | 0 Comments

Leave a comment

春來不似春

About

Eventstream

Pages

Guestbook

Search

Site Admin

Recent Comments