现在的位置: 首页 > 综合 > 正文

UVA1440 有下界的最小流

2018年03月17日 ⁄ 综合 ⁄ 共 4106字 ⁄ 字号 评论关闭

题意很简单:

给出一张有向图,每次你可以从图中的任意一点出发,经过若干条边后停止,然后问你最少走几次可以将图中的每条边都走过至少一次,并且要输出方案

这个转化为网络流的话,就相当于 求一个最小流,并且存在下界,即每条边至少走一次

这让我联想到很久之前的一道题,也是有向图,问走多少条路径可以将整个图中的每条边都走过,但是跟本题不同的是,那题是不允许重复走边的。

那道题目的解是这样的:

对于图中的每个点i,设D[i]为(i的入度-i的出度)的值,按照D[i]将图中的点分类:D[i]<0的称为“入少出多”的点,D[i]>0的称为“出少入多”的点,D[i]=0的称为“入出相等”的点。则有:
定理 有向无环图中最小边路径覆盖的值等于图中所有“入少出多”的点的D值之和。
证明:
其实只需证明:对于一个至少有一条边的有向无环图,必然存在一条路径,其起点是“入少出多”的点,终点是“出少入多”的点,所有中间点都是“入出相等”的点(只要不断的在图中找到并删去这条路径,直到图中无边为止)。
首先,由于图中无环,一定存在“入少出多”的点和“出少入多”的点。
然后,假设图中所有“入少出多”的点的后继(注意:后继和后代是不同的,一个点的后代包括这个点的所有后继、所有后继的后继、所有后继的后继的后继……)也都是“入少出多”的点,则图中所有“入少出多”的点构成了一个闭合子图,在这个闭合子图中,由于所有的点都是“入少出多”的,整个子图的入度之和必然大于出度之和,这是不可能的(有向图中的所有点入度之和必然等于所有点出度之和),故可得:图中必然存在至少一个“入少出多”的点,它有不是“入少出多”的后继。
这样,在这些拥有不是“入少出多”的后继的点中选择一个点i,将其作为路径的起点,在它的不是“入少出多”的后继中选出一个点j,若j是“出少入多”的点,则边<i, j>就是符合条件的一条路径,结束;若这样的j都是“入出相等”的点,则考虑j的后代:j的后继可能都是“入少出多”的,但j的后代中必然存在至少一个点j'不是“入少出多”的(否则j的所有后代也将构成全都是“入少出多”的闭合子图),这些j'中必然存在一个点的前趋i'是“入少出多”的,这是,需要舍弃原来的路径,将i'作为新路径的起点,j'作为新路径的第一个中间点,继续找;若j的后继不全是“入少出多”的,则若其中有“出少入多”的则路径已找到,若都是“入出相等”的,由于图中无环,将路径不断延伸,最终一定会找到合法路径。

为啥讲这道题呢,肯定跟本题有关系。

上面这个定理求出的解,肯定适用于本题。但是不一定是最优解,也就是有可能多走了若干次。

这个叫可行流,可以到对应一个建好的图中,如下所示:

设每个点i的入度减去出度为d[i], S为源点,T为汇点。
对于d[i] > 0的点i, 连边<i,T>
对于d[i] < 0的点i, 连边<S,i>
其它边连法与输入的边相同。

建好的这个图的最大流就是上边那个定理求出的解,假设为flow1。

然后有一个结论,求有下界的最小流的求法是:

首先要有可行流,然后倒向求解。

保持那个中间那些边方向不变,就是输入的那些边,

然后原来从源点连过来的点都连去汇点,原来连去汇点的都从源点连过去。

即对于d[i] < 0的点i, 连边<i,T>
对于d[i] >0的点i, 连边<S,i>

这其实啊 ,就是原来那个图,然后从T到S做最大流

假设这个求出的流量是flow2,则有下界的最小流的最优解是flow1-flow2

那么这是啥原理呢。

最开始的定理中说道对于一个至少有一条边的有向无环图,必然存在一条路径,其起点是“入少出多”的点,终点是“出少入多”的点,所有中间点都是“入出相等”的点  

那么其实我们找的每条路径都是起点入少出多,差为1,终点,出少入多,也是差为1

反向建图求的是什么呢,就是在新图中起点出少入多的,差为1,终点,入少出多,也是差为1的路径

然后对于每找到这样的一个流的路径,可以将原始建图中的两条路径合并,然后新路径的中间点正好都是出入度相等的,两头出入度不同相差为1

我们通过画图来验证这个

例如此图,从上到下从左到右编号1->6

按照最开始那个定理求出的解有3个路径

 1->3   2->3->4->5   4->6

然后你倒着建图 求出来了一条可以做衔接的路径 是3->4

1->3   4->6就被连接了

从而答案减去1

在画图的过程中,我们可以发现,对于每条这样的可以当做中间衔接的路径,一定可以合并原始建图中的两个路径。

然后本题的第一个问题就解决了

然后就是输出。

没有要求字典序。

具体的做法可以这样,在逆向的建图中,假如某条边走流量走了x ,则说明整个过程中该边被走了x+1次,

那么可以构造新图,同样的位置加入一条容量为x+1的边,

可以计算出这个新图的所有的d[i],对于此图,就可以使用第一个定理来做了,必然得到最优解,

在具体实现中,可以通过dfs来寻找路径,以某点为开始时,能走就走,一直走到不能走为止,注意可能从某个点发出多个路径,

并且在走的时候需要注意的是,由于我们要删掉这条路径,会导致路径的开头和结尾的d发生变化,需要作出修改

代码参考了一下网上的一个人http://blog.csdn.net/auto_ac/article/details/9398657

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <ctime>
#include <set>
#include <vector>
#include <map>
#define MAXN 111
#define MAXM 55555
#define INF 1000000007
using namespace std;
struct EDGE {
    int v, next;
    int w;
}edge[MAXM];
int head[MAXN], e;
void init() {
    memset(head, -1, sizeof(head));
    e = 0;
}
void add(int u, int v, int w) {
    edge[e].v = v;
    edge[e].w = w;
    edge[e].next = head[u];
    head[u] = e++;
    edge[e].v = u;
    edge[e].w = 0;
    edge[e].next = head[v];
    head[v] = e++;
}
int n, nt;
int h[MAXN];
int gap[MAXN];
int src, des;
int dfs(int pos, int cost) {
    if(pos == des) return cost;
    int j, minh = n - 1;
    int lv = cost, d;
    for(j = head[pos]; j != -1; j = edge[j].next) {
        int v = edge[j].v;
        int w = edge[j].w;
        if(w > 0) {
            if(h[v] + 1 == h[pos]) {
                if(lv < edge[j].w) d = lv;
                else d = edge[j].w;
                d = dfs(v, d);
                edge[j].w -= d;
                edge[j ^ 1].w += d;
                lv -= d;
                if(h[src] >= n) return cost - lv;
                if(lv == 0) break;
            }
            if(h[v] < minh) minh = h[v];
        }
    }
    if(lv == cost) {
        --gap[h[pos]];
        if(gap[h[pos]] == 0) h[src] = n;
        h[pos] = minh + 1;
        ++gap[h[pos]];
    }
    return cost - lv;
}
int sap() {
    int ret = 0;
    memset(gap, 0, sizeof(gap));
    memset(h, 0, sizeof(h));
    gap[0] = n;
    while(h[src] < n) ret += dfs(src, INF);
    return ret;
}
int d[MAXN];
typedef pair<int, int> PII;
vector<PII> g[MAXN];
int ans[MAXN];
int cnt, flag;
void dfs(int u) {
    int f = 0;
    ans[cnt++] = u;
    for(int i = 0; i < g[u].size(); i++) {
        if(flag) return;
        int v = g[u][i].first;
        if(!g[u][i].second) continue;
        f = 1;
        --g[u][i].second;
        dfs(v);
    }
    if(!f) d[u]--, flag = 1;
}
int main()
{
    int u, v, t;
    while(scanf("%d", &nt) != EOF) {
        init();
        memset(d, 0, sizeof(d));
        for(int i = 1; i <= nt; i++) {
            scanf("%d", &t);
            while(t--) {
                scanf("%d", &v);
                d[i]--;
                d[v]++;
                add(i, v, INF);
            }
        }
        src = nt + 1;
        des = nt + 2;
        n = nt + 2;
        int res = 0;
        for(int i = 1; i <= nt; i++) {
            if(d[i] > 0) add(src, i, d[i]);
            else if(d[i] < 0) add(i, des, -d[i]), res -= d[i];
        }
        printf("%d\n", res - sap());
        for(int i = 1; i <= nt; i++) g[i].clear();
        for(int i = 1; i <= nt; i++) {
            for(int j = head[i]; j != -1; j = edge[j].next) {
                if((j & 1) || edge[j].v > nt) continue;
                g[i].push_back(make_pair(edge[j].v, edge[j ^ 1].w + 1));
                d[i] -= edge[j ^ 1].w;
                d[edge[j].v] += edge[j ^ 1].w;
            }
        }
        for(int i = 1; i <= nt; i++) {
            while(d[i] < 0) {
                cnt = 0;
                flag = 0;
                d[i]++;
                dfs(i);
                for(int j = 0; j < cnt; j++) {
                    printf("%d", ans[j]);
                    if(j == cnt - 1) printf("\n");
                    else printf(" ");
                }
            }
        }
    }
	return 0;
}

抱歉!评论已关闭.