美文网首页动态规划
JZOJ4371. 【GDOI2016模拟】作业分配 题解 (动

JZOJ4371. 【GDOI2016模拟】作业分配 题解 (动

作者: ZJL_OIJR | 来源:发表于2018-07-15 21:49 被阅读47次

    题面

    题面

    思路

    一眼的费用流模型,建立超级源点S连向所有的科目,容量为该科目份数,费用为0,建立超级汇点T,将所有人连向超级汇点,容量为人最多做的份数,费用为0,将第i个题目连向第j个人,容量为\infty,花费就是题目里的c_{i,j}。至于为什么这样连,我把样例对应的网络放在下面,你可以对着图模拟,然后就能明白对应的容量和费用的意义了。(括号中第一个数是容量,第二个数是费用。注:将人连向题目与这种构图方法是等价的,没有太大区别):

    样例
    红红火火恍恍惚惚,这道题就这样切掉了,诶等等为什么超时?
    超时啦
    让我们冷静分析一波复杂度吧!
    众所周知,EK算法的复杂度是O(nm^2)的,在这题变态的构图下,m达到顶峰即m=n^2,复杂度就炸成O(n^5),这要怎么破?
    现在超级无敌望尘莫及令人望其项背的神奇优化——动态加边登场啦!

    动态加边的基本思路是:一开始只加入一部分边进行增广,如果某些边满流,就继续往里面加边,不断这样做。

    对于这道题:我们用数组E[i]存下与左边第i个点与右边所有的点所连的边,对每个E[i]按照花费从小到大排序。先把左边每个点i的E[i]中花费最小的边加进图,然后跑EK。记E[i][j].to表示i的第j条边所连接的右边的点,E[i][j].dist表示i的第j条边的花费,cnt[i]表示i上一次加进去的是第几条边。接着检查左边每个点i,如果E[i][cnt[i]].to连向T的边满流时,我们必须添加新边,不然有可能无法增广。于是就从cnt[i]开始从小到大加边,一直到E[i][cnt[i]].to连向T的边没有满流或者已经全部加完为止。加完以后继续跑EK。

    至于这样为什么能够提高效率。你可以这样理解吧(实在是想不到好的数学证明了),我们是按照从小到大的顺序加边,所以S到T的最短路更有可能在边数较少的情况下找到,这样效率就可以提高。并且这样的加边顺序令后面所加的边对最短路的影响较小,使bfs的过程效率提高。程序运行时间就-1500ms了(笑)。

    代码:

    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    #include <algorithm>
    using namespace std;
    inline int read()
    {
        int x = 0, f = 0;
        char c = getchar();
        for (; c < '0' || c > '9'; c = getchar()) if (c == '-') f = 1;
        for (; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ '0');
        return f ? -x : x;
    }
    inline int min(int a, int b) { return a < b ? a : b; }
    inline int max(int a, int b) { return a > b ? a : b; }
    
    typedef long long ll;
    const int N = 2e2 + 7, INF = 0x3f3f3f3f;
    
    struct edge { int to, dist; } c[N][N];
    int cnt[N], edgepre[N];
    int cmp(edge p, edge q) { return p.dist < q.dist; }
    
    int n, m, a[N], b[N];
    int tot = 1, st[N * 20], to[N * N * 20], nx[N * N * 20], len[N * N * 20];
    ll ans, cost[N * N * 20];
    
    inline void add(int u, int v, int w, int c)
    {
        to[++tot] = v, nx[tot] = st[u], len[tot] = w, cost[tot] = c, st[u] = tot;
        to[++tot] = u, nx[tot] = st[v], len[tot] = 0, cost[tot] = -c, st[v] = tot;
    }
    
    ll dis[N * 20];
    int head, tail, que[N * 10000], vis[N * 20], maxflow[N * 20], pre[N * 20], S, T;
    int bfs()
    {
        head = 1, tail = 0;
        memset(dis, 0x3f, sizeof(dis));
        memset(vis, 0, sizeof(vis));
        memset(pre, 0, sizeof(pre));
        memset(maxflow, 0, sizeof(maxflow));
        dis[S] = 0, que[++tail] = S, vis[S] = 1, maxflow[S] = INF;
        while (head <= tail)
        {
            int u = que[head++];
            vis[u] = 0;
            for (int i = st[u]; i; i = nx[i])
            {
                int v = to[i];
                if (len[i] > 0 && dis[u] + cost[i] < dis[v])
                {
                    maxflow[v] = min(maxflow[u], len[i]), dis[v] = dis[u] + cost[i], pre[v] = i;
                    if (!vis[v]) que[++tail] = v, vis[v] = 1;
                }
            }
        }
        return maxflow[T];
    }
    
    void solve()
    {
        while (bfs())
        {
            int now = T;
            while (now) len[pre[now]] -= maxflow[T], len[pre[now] ^ 1] += maxflow[T], now = to[pre[now] ^ 1];
            for (int i = 1; i <= n; i++)
                while (!len[edgepre[c[i][cnt[i]].to]] && cnt[i] < m)
                    cnt[i]++, add(i, c[i][cnt[i]].to + n, INF, c[i][cnt[i]].dist);
            ans += maxflow[T] * dis[T];
        }
        printf("%lld\n", ans);
    }
    
    void make_graph()
    {
        S = 0, T = n + m + 1;
        for (int i = 1; i <= n; i++) add(S, i, a[i], 0);
        for (int i = 1; i <= m; i++) add(i + n, T, b[i], 0), edgepre[i] = tot - 1;
        for (int i = 1; i <= n; i++)
        {
            sort(c[i] + 1, c[i] + m + 1, cmp);
            add(i, c[i][1].to + n, INF, c[i][1].dist);
            cnt[i] = 1;
        }
    }
    
    void init()
    {
        n = read(), m = read();
        for (int i = 1; i <= n; i++) a[i] = read();
        for (int i = 1; i <= m; i++) b[i] = read();
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                c[i][j].dist = read(), c[i][j].to = j;
    }
    
    int main()
    {
        init();
        make_graph();
        solve();
        return 0;
    }
    

    相关文章

      网友评论

        本文标题:JZOJ4371. 【GDOI2016模拟】作业分配 题解 (动

        本文链接:https://www.haomeiwen.com/subject/iekmpftx.html