zoukankan      html  css  js  c++  java
  • 【算法】01分数规划 --- HNOI2009最小圈 & APIO2017商旅 & SDOI2017新生舞会

      01分数规划:通常的问法是:在一张有 (n) 个点,(m) 条边的有向图中,每一条边均有其价值 (v) 与其代价 (w);求在图中的一个环使得这个环上所有的路径的权值和与代价和的比率最小大。即求 (frac{sum v}{sum w}) 的最小值最大值。

      通常的解法也是比较固定的,我们首先假设求最大值,最优的答案为 (L),(L = frac{sum v}{sum w})。接下来我们对于这个式子进行变形:

    (L * sum w = sum v)

    (L * sum w - sum v = 0)

      注意到这里面的 (L) 是我们假设出来的最优解。若我们二分这个答案,设我们当前二分出来的答案为 (R),则考虑当 (R < L) 和 (R > L) 的时候会分别出现什么状况。当我们选定了一个 (R),一条边的 (R * w - v) 就是一个固定的值,不妨将它视作新的边权。而原式便是这些边权的和。若此时图中有负环,因为 (R * w - v) 随 (R) 的增大单调不减,说明可以将 (R) 的值设定得更大,有更有的解。存在零环说明此时的 (R) 恰好等于 (L),没有负环 & 零环说明不能取到当前值。

     

    1.HNOI2009最小圈

      裸的01分数规划,只是最大变成了最小。做法还是一样,将spfa转化为最长路判正环即可。

    #include <bits/stdc++.h>
    using namespace std;
    #define maxn 10050
    #define eps 0.0000000001
    #define db double
    int n, m, cnp = 1, head[maxn];
    db ans, dis[maxn];
    bool vis[maxn], mark[maxn];
    
    struct edge
    {
        int to, last;
        db co, w;
    }E[maxn];
    
    void add(int u, int v, db w)
    {
        E[cnp].to = v, E[cnp].w = w;
        E[cnp].last = head[u], head[u] = cnp ++;
    }
    
    bool spfa(int u)
    {
        vis[u] = 1, mark[u] = 1;
        for(int i = head[u]; i; i = E[i].last)
        {
            int v = E[i].to;
            if(dis[v] < dis[u] + E[i].co) 
            {
                dis[v] = dis[u] + E[i].co;
                if(vis[v] || spfa(v)) { vis[u] = 0; return 1; }
            }
        }
        vis[u] = 0;
        return 0;
    }
    
    bool check()
    {
        memset(mark, 0, sizeof(mark));
        memset(dis, 0, sizeof(dis));
        for(int i = 1; i <= n; i ++)
            if(!mark[i] && spfa(i)) return 1;
        return 0;
    }
    
    int main()
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i ++)
        {
            int u, v; db w;
            scanf("%d%d%lf", &u, &v, &w);
            add(u, v, w);
        }
        db l = -100000, r = 100000;
        while(l + eps < r)
        {
            db mid = (l + r) / 2.0;
            for(int i = 1; i < cnp; i ++) E[i].co = mid - E[i].w;
            if(check()) ans = mid, r = mid;
            else l = mid;
        }
        printf("%.8lf
    ", ans);
        return 0;
    } 

    2.APIO2017商旅

      其实也是裸裸的一道题,建图的方式略有隐藏罢了。注意到两个点之间只能携带一种物品,我们将这些路径建成边连起来,然后套路即可。只不过因为这题我二分的是整数,所以要注意判零环。

    #include <bits/stdc++.h>
    using namespace std;
    #define maxn 105
    #define INF 99999999
    int n, m, T, a[maxn][maxn * 10][2];
    int R[maxn][maxn], val[maxn][maxn];
    int ans, E[maxn][maxn], dis[maxn];
    bool mark[maxn], vis[maxn];
    
    int read()
    {
        int x = 0, k = 1;
        char c;
        c = getchar();
        while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * k;
    }
    
    void Floyd()
    {
        for(int k = 1; k <= n; k ++)
            for(int i = 1; i <= n; i ++)
                for(int j = 1; j <= n; j ++)    
                    R[i][j] = min(R[i][j], R[i][k] + R[k][j]);
    }
    
    void Build()
    {
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
            {
                if(R[i][j] >= INF) continue;
                for(int k = 1; k <= T; k ++)
                    if(~a[i][k][0] && ~a[j][k][1]) 
                    {
                        int v = a[j][k][1] - a[i][k][0];
                        if(v > val[i][j]) val[i][j] = v;
                    }
            }
    }
    
    bool spfa(int u)
    {
        vis[u] = 1, mark[u] = 1;
        for(int i = 1; i <= n; i ++)
        {
            if(i == u || R[i][u] >= INF) continue;
            if(dis[i] > dis[u] + E[i][u] || (!mark[i] && dis[i] == dis[u] + E[i][u]))
            {
                dis[i] = dis[u] + E[i][u];
                if(vis[i] || spfa(i)) { vis[u] = 0; return 1;}
            }
            else if(dis[i] == dis[u] + E[i][u] && vis[i])
            { vis[u] = 0; return 1; }
        }
        vis[u] = 0;
        return 0;
    }
    
    bool check()
    {
        memset(dis, 0, sizeof(dis));
        memset(mark, 0, sizeof(mark));
        for(int i = 1; i <= n; i ++)
            if(!mark[i] && spfa(i)) return 1;
        return 0;
    }
    
    void work()
    {
        int l = 1, r = 5000;
        while(l <= r)
        {
            int mid = (l + r) >> 1;
            for(int i = 1; i <= n; i ++)
                for(int j = 1; j <= n; j ++)
                {
                    if(R[i][j] >= INF) continue;
                    E[i][j] = mid * R[i][j] - val[i][j];
                }
            if(check()) ans = mid, l = mid + 1;
            else r = mid - 1;
        }
    }
    
    int main()
    {
        n = read(), m = read(), T = read();
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= 2 * T; j ++)
                if(j & 1) a[i][(j >> 1) + 1][0] = read();
                else a[i][j >> 1][1] = read();
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                if(i != j) R[i][j] = INF; 
        for(int i = 1; i <= m; i ++)
        {
            int x = read(), y = read(), t = read();
            R[x][y] = min(R[x][y], t);
        }
        Floyd();
        Build();
        work(); 
        printf("%d
    ", ans);
        return 0; 
    } 

     3.SDOI2017新生舞会

      就套路吧……只不过要注意下精度。【我的代码跑得非常非常的慢……】

    #include <bits/stdc++.h>
    using namespace std;
    #define maxn 450
    #define INF 9999999999.9
    #define int long long
    #define db double
    #define eps 0.00000001
    
    int n, a[maxn][maxn], b[maxn][maxn];
    int flow[maxn], S, T, pre[maxn];
    int cnp, head[maxn];
    deque <int> q; bool vis[maxn], flag;
    db dis[maxn], cost;
    
    struct edge
    {
        int to, last, u, f, F;
        db co;
    }E[maxn * 400];
    
    int read()
    {
        int x = 0, k = 1;
        char c;
        c = getchar();
        while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); }
        while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * k;
    }
    
    void add(int u, int v, int f, int co)
    {
        E[cnp].u = u, E[cnp].to = v, E[cnp].co = (db) co; 
        E[cnp].last = head[u]; E[cnp].f = E[cnp].F = f; head[u] = cnp ++;
        E[cnp].u = v, E[cnp].to = u, E[cnp].co = -(db) co; 
        E[cnp].last = head[v]; E[cnp].f = E[cnp].F = 0; head[v] = cnp ++;
    }
    
    void init()
    {
        int a = 2 * n + 1;
        for(int i = 0; i <= a; i ++) dis[i] = INF;  
    }
    
    bool SPFA()
    {
        q.push_back(S); init();
        flow[S] = INF, vis[S] = 1, dis[S] = 0;
        while(!q.empty())
        {
            int u = q.front(); q.pop_front(); vis[u] = 0;
            for(int i = head[u]; ~i; i = E[i].last)
            {
                int v = E[i].to;
                if(E[i].f && dis[v] > dis[u] + E[i].co)
                {
                    dis[v] = dis[u] + E[i].co; pre[v] = i;
                    flow[v] = min(flow[u], E[i].f);
                    if(!vis[v])
                    {
                        vis[v] = 1;
                        if(!q.empty() && dis[v] < dis[q.front()]) q.push_front(v);
                        else q.push_back(v);
                    }
                }
            }
        }
        if(dis[T] >= INF) return 0; else return 1; 
    }
    
    db work()
    {
        cost = 0; int ans = 0;
        while(SPFA())
        {
            int e = pre[T];
            while(2333)
            {
                E[e].f -= flow[T], E[e ^ 1].f += flow[T];
                if(E[e].u != S) e = pre[E[e].u];
                else break;
            }
            ans += flow[T]; cost += (db) flow[T] * dis[T];
        }
        return cost;
    }
    
    signed main()
    {
        n = read(); T = 2 * n + 1;
        memset(head, -1, sizeof(head));
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                a[i][j] = read();
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                b[i][j] = read();
        for(int i = 1; i <= n; i ++)
            for(int j = 1; j <= n; j ++)
                add(i, j + n, 1, 0);
        for(int i = 1; i <= n; i ++) 
        {
            add(i + n, T, 1, 0);
            add(S, i, 1, 0); 
        }
        db r = 10000.0, l = eps, ans = 0;
        while(r - l > eps)
        {
            db mid = (r + l) / 2.0;
            for(int i = 0; i < cnp; i += 2)
            {
                int u = E[i].u, v = E[i].to; E[i].f = E[i].F, E[i ^ 1].f = E[i ^ 1].F;
                if(!u || !v || u == 2 * n + 1 || v == 2 * n + 1) continue;
                E[i].co = -((db) a[u][v - n] - mid * (db) b[u][v - n]);
                E[i ^ 1].co = - E[i].co; 
            }
            db mf = work();
            if(mf > 0) r = mid;
            else ans = mid, l = mid;
        }
        printf("%.6lf
    ", ans);
        return 0;
    }
  • 相关阅读:
    【2021-01-26】保持学习,也是一种科学的健康保养方式
    【2021-01-25】正常市场化的企业该有的现象
    day 42 mysql 数据库(2)
    day 41 数据库初学习
    在排序数组中查找元素的第一个和最后一个位置
    飞机游戏
    两个数组的交集 II
    重复N次的元素
    单词规律
    存在重复元素2
  • 原文地址:https://www.cnblogs.com/twilight-sx/p/9106319.html
Copyright © 2011-2022 走看看