JZOJ4371. 【GDOI2016模拟】作业分配 题解 (动态加边网络流经典题)

题面

题面

思路

一眼的费用流模型,建立超级源点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;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容

  • 我想,如果有来世,我愿做比翼鸟…… 比翼鸟,一目一翼,只有雌雄同行才能飞行! 即使一目一翼,我要做只比翼鸟! 我们...
    小洱朵妈妈阅读 212评论 0 0
  • 有一个瞬间, 你让我 怦然心动 是你的剪影 和不经意的 笑容。 很多年以后, 你找到我 在梦中, 那个眼神, 看不...
    克莱辛先生阅读 102评论 0 1
  • 那天,我要飞走了 你说,时间还早 拉着我飞奔去了街角的银匠铺 一起设计了款式, 每枚戒指半颗心,拼起来才是完整的爱...
    我的大猫梨子阅读 199评论 0 1