Codeforces 606E Freelancer's Dreams - 线性规划

来源:互联网 发布:mysql自定义排序规则 编辑:程序博客网 时间:2024/05/13 19:26

题目链接: http://codeforces.com/contest/606/problem/E


题目意思就是有个二货想成为一个程序员高手还想买套房,成为高手需要经验p,买套房需要金钱q,然而他现在啥都没有(经验金钱都为0),但是他有n个项目,对于每个项目每天可以得经验ai,金钱bi,但是一天只能做一个项目,一个项目做的天数可以是小数经验和金钱还是按照倍数得到, 问最少需要多少天可以实现他的梦想。


思路:

设每个项目做得天数分别为x1, x2, ... 那么根据题目有:Sum(ai * xi) >= p, Sum(bi * xi) >= q, 现在要求SUm(xi)最小, 这是个典型的线性规划问题, 所以第一种思路就是直接用单纯形法就可以求出答案。


#include <bits/stdc++.h>using namespace std;typedef long double DOUBLE;typedef vector<DOUBLE> VD;typedef vector<VD> VVD;typedef vector<int> VI;const DOUBLE EPS = 1e-9;struct LPSolver {    int m, n;    VI B, N;    VVD D;    LPSolver(const VVD &A, const VD &b, const VD &c) :        m(b.size()), n(c.size()), N(n + 1), B(m), D(m + 2, VD(n + 2)) {        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) D[i][j] = A[i][j];        for (int i = 0; i < m; i++) { B[i] = n + i; D[i][n] = -1; D[i][n + 1] = b[i]; }        for (int j = 0; j < n; j++) { N[j] = j; D[m][j] = -c[j]; }        N[n] = -1; D[m + 1][n] = 1;    }    void Pivot(int r, int s) {        for (int i = 0; i < m + 2; i++) if (i != r)            for (int j = 0; j < n + 2; j++) if (j != s)                D[i][j] -= D[r][j] * D[i][s] / D[r][s];        for (int j = 0; j < n + 2; j++) if (j != s) D[r][j] /= D[r][s];        for (int i = 0; i < m + 2; i++) if (i != r) D[i][s] /= -D[r][s];        D[r][s] = 1.0 / D[r][s];        swap(B[r], N[s]);    }    bool Simplex(int phase) {        int x = phase == 1 ? m + 1 : m;        while (true) {            int s = -1;            for (int j = 0; j <= n; j++) {                if (phase == 2 && N[j] == -1) continue;                if (s == -1 || D[x][j] < D[x][s] || D[x][j] == D[x][s] && N[j] < N[s]) s = j;            }            if (D[x][s] > -EPS) return true;            int r = -1;            for (int i = 0; i < m; i++) {                if (D[i][s] < EPS) continue;                if (r == -1 || D[i][n + 1] / D[i][s] < D[r][n + 1] / D[r][s] ||                    (D[i][n + 1] / D[i][s]) == (D[r][n + 1] / D[r][s]) && B[i] < B[r]) r = i;            }            if (r == -1) return false;            Pivot(r, s);        }    }    DOUBLE Solve(VD &x) {        int r = 0;        for (int i = 1; i < m; i++) if (D[i][n + 1] < D[r][n + 1]) r = i;        if (D[r][n + 1] < -EPS) {            Pivot(r, n);            if (!Simplex(1) || D[m + 1][n + 1] < -EPS) return -numeric_limits<DOUBLE>::infinity();            for (int i = 0; i < m; i++) if (B[i] == -1) {                int s = -1;                for (int j = 0; j <= n; j++)                    if (s == -1 || D[i][j] < D[i][s] || D[i][j] == D[i][s] && N[j] < N[s]) s = j;                Pivot(i, s);            }        }        if (!Simplex(2)) return numeric_limits<DOUBLE>::infinity();        x = VD(n);        for (int i = 0; i < m; i++) if (B[i] < n) x[B[i]] = D[i][n + 1];        return D[m][n + 1];    }};int main(){    int n, p ,q;    cin >> n >> p >> q;    VVD A(2, VD(n));    int ra, rb;    for(int i=0;i<n;i++)    {        cin >> ra >> rb;        A[0][i] = -ra;        A[1][i] = -rb;    }    VD B(2);    B[0] = -p;    B[1] = -q;    VD C(n, -1.0);    VD x;    LPSolver solver(A, B, C);    DOUBLE ans = -solver.Solve(x);    cout << setiosflags(ios::fixed);    cout << setprecision(10) << ans << endl;    return 0;}


第二种思路是根据题解的,将(a[i], b[i])看做平面上的点, 并且添加(0, max(b[i])), (max(a[i]), 0)两个点,然后求一遍凸包,最后将所有与(0,0)(p,q)相交的点作为平均速度完成梦想,其中最小的完成时间就是答案。

官方题解: http://codeforces.com/blog/entry/22019

We can let our hero not to receive money or experience for some projects. This new opportunity does not change the answer. Consider the hero spent time T to achieve his dream. On each project he spent some part of this time (possibly zero). So the average speed of making money and experience was linear combination of speeds on all these projects, weighted by parts of time spent for each of the projects.

Let’s build the set P on the plane of points (x, y) such that we can receive x money and y experience per time unit. Place points (a[i], b[i]) on the plane. Add also two points (max(a[i]), 0) and (0, max(b[i])). All these points for sure are included to P. Find their convex hull. After that, any point inside or at the border of the convex hull would correspond to usage of some linear combination of projects.

Now we should select some point which hero should use as the average speed of receiving money and experience during all time of achieving his dream. This point should be non-strictly inside the convex hull. The dream is realized if we get to point (A,B). The problem lets us to get upper of righter, but to do so is not easier than to get to the (A,B) itself. So let’s direct a ray from (0,0) to (A,B) and find the latest moment when this ray was inside our convex hull. This point would correspond to the largest available speed of receiving resources in the direction of point (A,B). Coordinates of this point are speed of getting resources.


#include <bits/stdc++.h>using namespace std;#define sqr(x) ((x)*(x))typedef long double ld;typedef long long ll;const ld eps = 1e-10;const int maxn = 1e5+10;struct Point{    ld x,y;    Point(ld a=0, ld b=0):x(a), y(b){}}p[maxn];int tot, n;ld P, Q;Point st[maxn];int top;// p0p1 * p0p2ld cross(Point p0, Point p1, Point p2){    Point a = Point(p1.x - p0.x, p1.y - p0.y);    Point b = Point(p2.x - p0.x, p2.y - p0.y);    return a.x * b.y - a.y * b.x;}int sgn(ld x){   return (x > eps) - (x < -eps);}ld dis(Point a, Point b){    return sqrt(sqr(a.x-b.x) + sqr(a.y - b.y));}bool cmp1(Point a, Point b){    if(sgn(a.x - b.x) == 0) return a.y < b.y;    return a.x < b.x;}bool cmp(Point a, Point b){    int t = sgn(cross(p[0], a, b));    if(  t == 0 ) return sgn( dis(p[0], a) - dis(p[0], b) ) < 0;    return t > 0;}void solve(){    sort(p, p+tot, cmp1);    sort(p+1, p+tot, cmp);    top = -1;    for(int i=0;i<tot;i++)    {        while(top >= 2 && sgn(cross(st[top-1], st[top], p[i])) <= 0) top--;        st[++top] = p[i];    }//    printf("convex hull:\n");//    for(int i=0;i<=top;i++)//    {//        cout << st[i].x << "  " << st[i].y << endl;//    }    st[top+1] = st[0];    ld res = 1e30;    for(int i=0; i<=top; i++)    {        if( sgn( cross(st[i], Point(0,0), Point(P,Q) ) ) *  sgn( cross(st[i+1], Point(0,0), Point(P,Q) )) <=0 )        {//            printf("%f %f\n", st[i].x, st[i].y);//            printf("%f %f\n", st[i+1].x, st[i+1].y);//            printf("%f %f\n", P, Q);            ld s1 = cross(st[i], Point(P,Q), st[i+1]);            ld s2 = cross(Point(0,0), st[i], st[i+1]);            //printf("%f %f\n", s1, s2);            res=min(res,(s1+s2+0.0)/s2);        }    }    cout <<setiosflags(ios::fixed);    cout << setprecision(10) << res << endl;}int main(){    cin >> n >> P >> Q;    ld x, y;    ld Mx = 0, My = 0;    tot = 0;    for(int i=0;i<n;i++)    {        cin >> x >> y;        p[tot++] = Point(x, y);        Mx = max(Mx, x);        My = max(My, y);    }    p[tot++] = Point(0, My);    p[tot++] = Point(Mx, 0);    solve();    return 0;}


最后一种方法是赛后杜宇浩大神给出来了根据对偶性,原问题的对偶问题就是求max( p*y1 + q*y2), 满足ai * y1 + bi * y2 <= 1。于是可以直接用三分求最大值!

原文:http://codeforces.com/blog/entry/22019

My solution to C:  subject to . The dual of this problem ismax p * y1 + q * y2 subject to ai * y1 + bi * y2 ≤ 1. It's convex, so we can use a simple ternary search.


#include <bits/stdc++.h>using namespace std;const int maxn = 1e5+10;int a[maxn], b[maxn];int n, p, q;double f(double x){    double y = 1;    for(int i=0;i<n;i++) y = min(y, (1.0 - x*b[i]) / a[i]);    return y*p + x*q;}int main(){    cin >> n >> p >> q;    for(int i=0;i<n;i++) cin >> a[i] >> b[i];    double l=0, r = 1./ (*max_element(b, b+n));    for(int i=0;i<50;i++)    {        double ll = (l+r)*0.5, rr = (ll + r)*0.5;        if(f(ll) > f(rr)) r = rr;        else l = ll;    }    printf("%.10f\n", f((l+r)*0.5));    return 0;}


0 1
原创粉丝点击