kuangbin专题之网络流

经过4天的折磨,终于写完了这个专题 …
点此进入专题地址
呃 感觉还是有所提升的,一直以为图论难的是建图方式,现在发现有一个优秀的板子也是十分必要的…不然TLE到自闭…
考虑到大多数都需要用到板子,所以下面只给出建图函数,不给出求最大流之类的函数了
——------------------------------------------------------------
A - ACM Computer Factory POJ - 3436
给出了 n n 台机器的信息,对于每一个机器有两个属性和一个价值
属性为:输入与输出,每个属性共由 p p 个小细节决定, p p 由键盘输入
求获得最大价值需要新建立哪些边
具体细节为
1. p o s = 0 pos=0 :不需要接口输入 / 不能输出接口
2. p o s = 1 pos=1 : 必须需要接口输入 / 输出一个接口
3. p o s = 2 pos=2 : 是否有接口输入无所谓
所以我们可以发现,对于输入细节是0/2的机器 均可以放在第一个作为总输入,对于输出细节全为1的机器,均可以作为总输出
首先求出最大价值,即最大流
所以我们建立一个超级源点和超级汇点,之后对于每一个机器拆点拆为两个点,边为i到i + n,权值为w[i]的有向边,然后把所有输入符合条件的点与源点建边为inf,输出符合条件的点拆点后与汇点建边为inf,之后对于所有输出与另一个输入吻合的点,则把输出拆点后与输入建边为inf,即i + n -> j ,(至于为什么是从拆完点之后建边,就需要细细品了…)
之后就是直接跑一个最大流了
然后需要找出新建立的边
因为我们在前面手动把每一个输出与输入吻合的点建边了,所以我们只需要求出哪些边被用过,然后输出就可以了,大概有两种办法,
1. 结构体不含流量,只含容量的话,那就找出所有终点小于起点,且flow大于0的边就可以了
2. 结构体设置了流量这一属性的话,就直接求出所有流量小于0的边就可以了
建图是
虚拟源点–>电器–>拆点电器–>虚拟汇点
inf–>w[i]–>inf
电器内部为
源点–>输入电器 输出电器–>汇点
之后在电器之中暴力枚举可以互相连接的点再暴力连接起来
输出点–>输入点
inf

vector<ANS> ans;
void solve () {
    int n, pp, q;
    int in[55][55], out[55][55], w[55];
    Dinic res;
    res.clear();
    //p = read(), n = read();
    scanf("%d %d", &pp, &n);
    int S = 0, T = 2 * n + 1;
    res.S = S, res.T = T;
    rep(i,1,n) {
        //w[i] = read();
        scanf("%d", &w[i]);
        bool flag = true;
        rep(j, 0, pp - 1) {
            //in[i][j] = read();
            scanf("%d",&in[i][j]);
            if(in[i][j] == 1) {
                flag = false;
            }
        }
        if(flag) {
            res.add_edge(S, i, inf);
        }
        flag = true;
        rep(j, 0 ,pp - 1) {
            //out[i][j] = read();
            scanf("%d", &out[i][j]);
            if(out[i][j] == 0) {
                flag = false;
            }
        }
        if(flag) {
            res.add_edge(i + n, T, inf);
        }
    }
    rep(i,1,n) {
        res.add_edge(i, i + n, w[i]);
        rep(j,1,n) {
            if(i == j) {
                continue;
            }
            if(judge(out[i],in[j],pp)) {
                res.add_edge(i + n, j, inf);
            }
        }
    }
    int sum = res.dinic() , cnt = 0;
    rep(i,0,res.num - 1) {
        int u = edge[i].u, v = edge[i].v, w = edge[i].c;
        if(u == T || u == S || v == T || v == S || u - n == v || v - n == u) {
            continue;
        }
        // 找到反向弧
        if((i & 1) && v > u && w > 0) {
            ++ cnt;
            ans.pb(ANS(v - n, u, w));
        }   
    }
    printf("%d %d\n",sum, cnt);
    rep(i,0,cnt - 1) {
        printf("%d %d %d\n",ans[i].u, ans[i].v, ans[i].w);
    }
}

B - Dining POJ - 3281
给出 n n 头牛,F个饮料,D个面包(每个面包和饮料均不同)
每头牛有自己所喜爱的饮料与面包,且两者都要吃一个
求最大可以满足多少牛
这个就很简单了,先把每头牛拆点,建立一条 i > i + n i->i+n 权值为1的边,然后建立一个超级源点与超级汇点,之后把所有的饮料与源点/喜爱他的牛建边,权值为1,同理把所有的面包与汇点/喜爱他的牛的拆点建边,权值为1,然后跑最大流
所以建立出来的图大致为
权值均为1
源点–>饮料–>牛–>拆点牛–>面包–>汇点

void solve () {
    scanf("%d %d %d", &n, &f, &d);
    N = n * 2 + f + d + 2;
    int S = n * 2 + f + d + 1, E = S + 1;
    rep(i,1,n) {
        int nf = read(), nd = read();
        while(nf --) {
            int pos = read();
            pos += n * 2;
            int u = i * 2 - 1;
            add(pos, u);
            add(u, u + 1);
            add(S, pos);
        }
        while(nd --) {
            int pos = read();
            pos += n * 2 + f;
            int u = i * 2;
            add(u, pos);
            add(pos, E);
        }
    }
    printf("%d\n",max_flow(S, E));
}

C - A Plug for UNIX POJ - 1087
首先用 m a p map 把这些字符串都转换为下标
。。。。
大意是先给出 n n 个插座,然后给出 m m 个用电器(每种用电器有它所必须的插座),最后给出 x x 个适配器,每行给出a,b 代表可以把b转换为a
适配器数目无限多,求问最多可以使多少用电器成功用上电
也就是求最大流,首先把各种字符串转换为下标…
然后由于这个是源点直接供给插座,所以不需要拆点了,直接源点链接插座
对于每一种适配器,我们建立一条从后者到前者权值为 i n f inf 的边就可以了
然后用电器连接汇点,求一个最大流就可以了
建图方式为
源点–>插座–>用电器–>汇点
关于每一种适配器我们可以放在插座部分
输出点–>输入点

map<string, int> mp;
void solve() {
    Dinic res;
    res.clear();
    int n, m, k;
    res.S = 0, res.T = 1;
    int S = 0, T = 1;
    int tot = 2;
    string s1, s2;
    cin >> n; while(n --) {
        cin >> s1;
        mp[s1] = tot ++;
        res.add_edge(S, mp[s1], 1);
    }
    cin >> m; rep(i,1,m) {
        cin >> s1 >> s2;
        if(!mp[s1]) {mp[s1] = tot ++;}
        if(!mp[s2]) {mp[s2] = tot ++;}
        res.add_edge(mp[s1], T, 1);
        if(mp[s1] != mp[s2]) {
            res.add_edge(mp[s2], mp[s1], 1);
        }
    }
    cin >> k; while(k --) {
        cin >> s1 >> s2;
        if(!mp[s1]) {mp[s1] = tot ++;}
        if(!mp[s2]) {mp[s2] = tot ++;}
        if(mp[s1] != mp[s2]) {
            res.add_edge(mp[s2], mp[s1], inf);
        }
    }
    cout << m - res.dinic() << endl;
}

D - Going Home POJ - 2195
给出一个地图, H H 代表房子, M M 代表人,下雨后每个人都要跑回房子,但是一个房子只能有一个人居住,每个人到房子的距离为两者的欧几里得距离,每个人回家需要花费一定路费,路费与距离成正比,系数为1,也就是说,跑多远花多少钱,求出所有人都跑到房子内所花费的路费最低为多少
··
这就是一个最小费用最大流板子题,模板自己找吧
建图大概为
源点–>每个人 权值为1,费用为0
每个人–>房子 权值为1,费用为欧几里得距离
房子–>汇点 权值为1, 费用为0
然后就是直接跑板子啦

char str[1005][1005];
vector<pii> house, kid;
void solve () {
    while(~scanf("%d %d", &n, &m) && n && m) {
        S = 0, T = 1;
        ms(head, -1);
        num = -1;
        house.clear();
        kid.clear();
        rep(i,1,n) {
            scanf("%s", str[i] + 1);
        }
        rep(i,1,n) {
            rep(j,1,m) {
                if(str[i][j] == 'H') {
                    house.pb(MP(i,j));
                } else if(str[i][j] == 'm') {
                    kid.pb(MP(i,j));
                }
            }
        }
        int szhouse = house.size(), szkid = kid.size();
        rep(i,0,szkid - 1) {
            add_edge(0, i + 2, 1, 0);
            add_edge(i + 2, 0, 0, 0);
            rep(j,0,szhouse - 1) {
                add_edge(i + 2, j + 2 + szkid, 1, abs(kid[i].fi - house[j].fi) + abs(kid[i].se - house[j].se));
                add_edge(j + 2 + szkid, i + 2, 0, -abs(kid[i].fi - house[j].fi) + -abs(kid[i].se - house[j].se));
            }
        }
        rep(i,0,szhouse-1) {
            add_edge(1, i + 2 + szkid, 0, 0);
            add_edge(i + 2 + szkid, 1, 1, 0);
        }
        max_flow__min_cost();
        printf("%d\n",min_cost);
    }
}

E - Minimum Cost POJ - 2516
n n 个店主, m m 个商铺, k k 种货物
对于每一种货物,给出了每个店所需要的,每个商铺所拥有的,以及从n到m货物为k的单位费用
求出是否可以供应,可以的话输出最低费用
首先我们直接判断所有的货物数目与所有的需求数目,看每种货物是否都满足需求,不满足直接输出-1,满足的话求一个最小费用最大流
建图:
货物一种一种来求,算k次
对于每一种货物,把源点与商铺之间建立一条边,权值为商铺的拥有量,费用为0,然后建立每个商铺到每个店铺的边,权值为店铺所需要的,费用为对应路费,然后建立从店铺到汇点的边,权值为需要的,费用为0
然后就直接跑MCMF啦
这题用快读一直被卡超时,换成scanf就过了,,,可能数据很(du)棒(liu)
含有一堆格外的空格与空行吧…

void solve () {
    while(~scanf("%d %d %d", &n, &m, &k) && (n || m || k)) {

        res.init(n + m + 1);
        ms(need,0);
        ms(have,0);
        ms(money,0);
        ms(goods,0);
        bool flag = true;
        for(int i = 1; i <= n; ++ i) {
            for(int j = 1; j <= k; ++ j) {
                scanf("%d", &need[i][j]);
                goods[j] += need[i][j];
            }
        }
        for(int i = 1; i <= m; ++ i) {
            for(int j = 1; j <= k; ++ j) {
                scanf("%d", &have[i][j]);
                goods[j] -= have[i][j];
            }
        }
        for(int l = 1; l <= k; ++ l) {
            for(int i = 1; i <= n; ++ i) {
                for(int j = 1; j <= m; ++ j) {
                    scanf("%d", &money[i][j][l]);
                }
            }
        }
        for(int i = 1; i <= k; ++ i) {
            if(goods[i] > 0) {
                flag = false;
                break;
            }
        }
        if(!flag) {
            puts("-1");
            continue;
        }
        int sum = 0;
        for(int l = 1; l <= k; ++ l) {
            res.init(n + m + 1);
            for(int i = 1; i <= m; ++ i) {
                res.add_edge(0, i, have[i][l], 0);
                res.add_edge(i, 0, 0, 0);
            }
            for(int i = 1; i <= n; ++ i) {
                for(int j = 1; j <= m; ++ j) {
                    res.add_edge(j, i + m, have[j][l], money[i][j][l]);
                    res.add_edge(i + m, j, 0, -1 * money[i][j][l]);
                }
                res.add_edge(i + m, m + n + 1, need[i][l], 0);
                res.add_edge(m + n + 1, i + m, 0, 0);
            }
            sum += res.MinCostMaxFlow(0, n + m + 1);
        }
        printf("%d\n", sum);
    }
}

F - Power Network POJ - 1459
这题题意太绕了…就是给出m个边,然后a个源点,b个汇点
之后建立一个超级源点,超级汇点,都连接起来就好了,读入太难了,我用的cin。。。

void solve () {
    int n, m, ns, nt;
    char ch;
    while(~scanf("%d %d %d %d", &n, &ns, &nt, &m)) {
        res.init(n + 2);
        int S = n + 1, T = n + 2;
        while(m --) {
            int u, v, w;
            char ch;
            cin >> ch >> u >> ch >> v >> ch >> w;
            res.add_edge(u, v, w);
        }
        while(ns --) {
            int v, w;
            //scanf("(%d)%d", &v, &w);
            cin >> ch >> v >> ch >> w;
            res.add_edge(S, v, w);
        }
        while(nt --) {
            int u, w;
            //scanf("(%d)%d", &u, &w);
            cin >> ch >> u >> ch >> w;
            res.add_edge(u, T, w);
        }
        printf("%d\n", res.max_flow(S, T));
    }
}

G - Island Transport HDU - 4280
给出n个岛屿的坐标,以及m条边代表点和点之间的权值,源点为x最小的点,汇点为x最大的点,求一个最大流就可以了。。
问题是这题太毒瘤了,把Dinic卡了…必须要用ISAP算法。。。。吐了
我还不会ISAP,我用的别人写法9999ms过的。。。
迷惑的是我找来一个ISAP板子也超时了,,太迷惑了

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        int Max=0,Min=inf,s,t;
        init();
        for(int i=0; i<n; i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            if(Max<x) Max=x,t=i+1;
            if(Min>x) Min=x,s=i+1;
        }
        for(int i=0; i<m; i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);
        }
        printf("%d\n",Dinic(s,t));
    }
}

H - Food HDU - 4292
N N 种食物, M M 种饮料,每人有自己喜欢的食物与饮料,求问最多满足多少人
就是个简单最大流啊,食物与饮料之间用人塞进去,然后把人拆点,就可以了啊,问题是我一直谜之mle,最后把结构体封装删了就过了,,,
建图
源点–>食物–>人–>拆点人–>饮料–>汇点

void solve () {
    //int N, F, D;
    while(~scanf("%d %d %d", &n, &F, &D)) {
        clear();
        //读入饮料与食物的个数
        rep(i,1,F) {
            int tmp; scanf("%d", &tmp);
            add_edge(0, i, tmp);
        }
        rep(i,1,D) {
            int tmp; scanf("%d", &tmp);
            add_edge(i + F + 2 * n, T, tmp);
        }
        rep(i,1,n) {
            add_edge(F + i, F + n + i, 1);
            scanf("%s", str1[i] + 1);
        } 
        rep(i,1,n) {
            scanf("%s", str2[i] + 1);
        }
        rep(i,1,n) {
            rep(j,1,F) {
                if(str1[i][j] != 'Y') {
                    continue;
                }
                add_edge(j, i + F, 1);
            }
            rep(j,1,D) {
                if(str2[i][j] != 'Y') {
                    continue;
                }
                add_edge(i + F + n, j + F + n * 2, 1);
            }
        }
        printf("%d\n", dinic());
    }
}

I - Control HDU - 4289
给出点数 n n 与边数 m m ,以及源点汇点,和边权
求问最小割(就是这个意思,堵住一些边使得整体为0)
也就是最大流问题,关于最小割就是最大流可以自己去搜一搜证明

int arr[maxn];
void solve () {
    int n, m, s, t;
    while(~scanf("%lld %lld", &n, &m)) {
        scanf("%lld %lld", &s, &t);
        res.init(n * 2);
        rep(i,1,n) {
            scanf("%lld", &arr[i]);
            //if(i != t)
            res.add_edge(i * 2 - 1, i * 2, arr[i]);
            //if(i != s)
            res.add_edge(i * 2, i * 2 - 1, arr[i]);
            //cout << i * 2 - 1 << " " << i * 2 << "  " << arr[i] << endl;
        }
        while(m --) {
            int u, v;
            scanf("%lld %lld", &u, &v);
            res.add_edge(u * 2, v * 2 - 1, inf);
            res.add_edge(v * 2, u * 2 - 1, inf);
        }
        printf("%lld\n",res.max_flow(s * 2 - 1, t * 2));
    }
}

J - Sabotage UVA - 10480
这个和上面一样,也是需要求最小割
区别在于这个需要你输出被阻断的边,这个其实简单,在跑完最大流之后
从源点开始进行一次搜索,把它能到达的所有点进行染色,最后遍历所有的边,如果这个边残量为0,且两点颜色不同,则为被阻断的边,输出就可以了

    void search(int now) {
        pos[now] = true;
        int sz = G[now].size();
        for(int i = 0; i < sz; ++ i) {
            Edge& e = edge[G[now][i]];
            if(!pos[e.to] && e.cap > e.flow) {
                pos[e.to] = true;
                search(e.to);
            }
        }
    }
    void print() {
        m = edge.size();
        for(int i = 0; i < m; ++ i) {
            int u = edge[i].fr, v = edge[i].to;
            if(pos[u] != pos[v] && u > v) {
                printf("%d %d\n", u, v);
            }
        }
        puts("");
    }
    void solve () {
    int n, m, s, t;
    while(~scanf("%d %d", &n, &m) && n && m) {
        res.init(n);
        while(m --) {
            int u , v, w;
            scanf("%d %d %d", &u, &v, &w);
            res.add_edge(u, v, w);
        }
        res.max_flow(1, 2);
        res.search(1);
        res.print();
    }
}

K - Leapin’ Lizards HDU - 2732
n n 个人要逃生, m m 个柱子,每个柱子只能被踩一定次数,求问有多少人跑不出去,每次跑的最大距离为d
首先把所有一步能跑出去的点拆点后与汇点相连,点与拆点之间是它的被踩次数
然后把所有的人所在的地方与源点相连为1,之后暴力合并点,把距离小于D的点都加上边,注意是起点拆点后加给终点
最后求最大流,用总数减去他就好了

char str[50], mp[25][25];
int cas = 0;
void solve () {
    int n, m;
    double d;
    scanf("%d %lf", &n, &d);
    res.init(n * 20 * 2 + 2);
    int S = n * 20 * 2 + 1, T = n * 20 * 2 + 2;
    rep(i,1,n) {
        scanf("%s", str + 1);
        int len = strlen(str + 1);
        m = len;
        for(int j = 1; j <= len; ++ j) {
            if(str[j] != '0') {
                res.add_edge((i - 1) * len + j, (i - 1) * len + j + n * len, str[j] - '0');
                if(i - d < 1 || i + d > n || j - d < 1 || j + d > len) {
                    res.add_edge( (i - 1) * len + j + n * len, T, inf);
                }
            }
        }
    }
    int tot = 0;
    rep(i,1,n) {
        scanf("%s", str + 1);
        int len = strlen(str + 1);
        for(int j = 1; j <= len; ++ j) {
            if(str[j] == 'L') {
                ++ tot;
                res.add_edge(S, (i - 1) * len + j, 1);
            }
        }
    }
    //int m = strlen(str + 1);
    rep(i,1,n) {
        rep(j,1,m) {
            rep(x,1,n) {
                rep(y,1,m) {
                    double dis = sqrt((i - x) * (i - x) * 1.0 + (j - y) * (j - y));
                    if(dis > d) continue;
                    res.add_edge(n * m + (i - 1) * m + j, (x - 1) * m + y, inf);
                }
            }
        }
    }
    int tmp = res.max_flow(S, T);
    tmp = tot - tmp;
    printf("Case #%d: ", ++ cas);
    if(tmp == 0) {
        printf("no ");
    } else {
        printf("%d ",tmp);
    }
    if(tmp > 1) {
        printf("lizards were left behind.\n");
    } else {
        printf("lizard was left behind.\n");
    }
}

L咕咕咕 神仙题,读不懂
M - Escape HDU - 3605
给出 n n 个人, m m 个星球, 之后 n n 行,每行 m m 个字,为1则代表i可以在j星球生存,最后一行给出每个星球最多可以住多少人,求问是否可以使得所有人都有地方住
我是用二分图多重匹配做的,m大小只有10嗷嗷嗷
直接匈牙利多重匹配啦

void solve () {
    while(~scanf("%d %d", &n, &m)) {
        init();
        for(int i = 1; i <= n; ++ i) {
            for(int j = 1; j <= m; ++ j) {
                int tmp; scanf("%d", &tmp);
                if(tmp) {
                    G[i].push_back(j);
                }
            }
        }
        for(int i = 1; i <= m; ++ i) {
            scanf("%d", &sum[i]);
            if(sum[i] == 0) {
                sum[i] = -1;
            }
        }
        puts(match() ? "YES" : "NO");
    }
}

我用HK一直超时。。。匈牙利却过了
看来对于多重匹配来说,这个复杂度还是不太确定的嗷
N - Marriage Match II HDU - 3081
给出 n n 个妹子和 m m 个汉子,每个妹子有自己心仪的对象,每个妹子也有许多朋友,朋友关系可以传递(并查集),然后自己朋友心仪的对象自己也可以上(???呕吼??)
每一轮每个妹子匹配一个可以匹配的汉子,然后下一轮替换,不能匹配自己前面已经匹配到的(渣女!),求问可以玩多少轮(每一轮必须对于妹子要是完备匹配)
我本来想用最短路计数来做的,后来一直不过样例才发现用过的不能再用了,,,所以还是老老实实建图权值为1跑最大流吧
采用二分逼近答案,每次一check的mid值,我们从源点到每个妹子建立一个mid流量,妹子到汉子建立流量为1,汉子到汇点建立mid流量,最后judge的是最大流是否大于等于mid就可以啦

int dad[maxn];
bool line[maxn][maxn];
int seek(int x) {return x == dad[x] ? x : dad[x] = seek(dad[x]);}
bool judge(int n, int mid) {
    //Dinic res;
    res.init(n * 2 + 1);
    int S = 0, T = 2 * n + 1;
    for(int i = 1; i <= n; ++ i) {
        res.add_edge(S, i, mid);
        for(int j = n + 1; j <= n * 2; ++ j) {
            //res.add_edge(j, T, mid);
            if(line[i][j]) {
                res.add_edge(i, j, 1);
            }
        }
    }
    for(int j = n + 1; j <= n * 2; ++ j) {
        res.add_edge(j, T, mid);
    }
    return res.max_flow(S, T) >= mid * n;
}
void solve () {
    ms(line, false);
    int n, m, f;
    scanf("%d %d %d", &n, &m, &f);
    for(int i = 0; i <= n * 2 + 1; ++ i) {
        dad[i] = i;
    }
    while(m --) {
        int u, v;
        scanf("%d %d", &u, &v);
        line[u][v + n] = true;
    }
    while(f --) {
        int u, v;
        scanf("%d %d", &u, &v);
        dad[seek(u)] = seek(v);
    }
    for(int i = 1; i <= n; ++ i) {
        for(int j = i + 1; j <= n; ++ j) {
            if(seek(i) == seek(j)) {
                for(int k = n + 1; k <= 2 * n; ++ k) {
                    line[i][k] = line[j][k] = (line[i][k] || line[j][k]);
                }
            }
        }
    }
    int res = 0, L = 0, R = 100;
    //n = n * 2 + 1;
    while(L <= R) {
        int mid = L + R >> 1;
        if(judge(n, mid)) {
            res = mid;
            L = mid + 1;
        } else {
            R = mid - 1;
        }
    }
    printf("%d\n", res);
}

O - Marriage Match IV HDU - 3416
这个就是最短路计数问题了,不过由于路径不能重复走,所以也只能用最大流来做了,先跑最短路,然后对于每条边,如果dis[u]+w=dis[v],那就加入边进去,权值为1,最后跑最大流,

void solve () {
    res1.read_data();
    res1.dijstra(res1.S);
    res2.init(res1.n);
    for(int i = 1; i <= res1.n; ++ i) {
        int sz = res1.G[i].size();
        for(int j = 0; j < sz; ++ j) {
            decltype((res1.G[i][j])) e = res1.G[i][j];
            if(res1.dis[i] + e.w == res1.dis[e.v]) {
                res2.add_edge(i, e.v, 1);
            }
        }
    }
    printf("%d\n", res2.max_flow(res1.S, res1.T));
}

猜你喜欢

转载自blog.csdn.net/leoxe/article/details/107191692