最大流建图习题

最大流
给定指定的一个有向图,其中有两个特殊的点源S(Sources)和汇T(Sinks),每条边有指定的容量(Capacity),求满足条件的从S到T的最大流(MaxFlow)。
最小割
割是网络中定点的一个划分,它把网络中的所有顶点划分成两个顶点集合S和T,其中源点s∈S,汇点t∈T。记为CUT(S,T),满足条件的从S到T的最小割(Min cut)。

UVA10480

题意:
n<=50个城市, m<=500条边, 切断一条边所需要的花费。
现在想问你问最少花费多少来切边,使得1号点和2号点分开。
思路:
最小割最大流定理
在一个网络流中,能够从源点到达汇点的最大流量等于如果从网络中移除就能够导致网络流中断的边的集合的最小容量和。即在任何网络中,最大流的值等于最小割的容量

Lightoj 1154

题意:
二维坐标上有N(<=100)个点, 每个点有两个值,一个值是在这个点上的企鹅数量,另一个值是能从这个点跳到另一个点的最大数量,一个点能跳的点的距离<=D, 现在让你求存在哪些点可以使得所有企鹅集合。
思路:
利用D值可以对所有点之间建边,容量就是能跳的最大数量。
具体建图呢?比如u有多个点a,b,c可以到达,肯定不可能是u和a,b,c都连一条边然后容量设置成限制值,这里要拆出一个点(u+n),先是u-(u+n)的边容量是限制值,然后(u+n)与a,b,c相连,边容量是INF,是不是还有点小技巧呢?
那本身的值呢?开一个超级源点和本身相连,边容量就是本身的值。
这样我对于这幅图,枚举一下汇点,跑一下最大流算法判断一下是不是等于企鹅总数就好了。

HDU 5294

题意:
给出n(<=2000)个墓室,m条路径,一个人在1号墓室(起点),另一个人在n号墓室(终点);
起点的那个人只有通过最短路径才能追上终点的那个人,而终点的那个人能切断任意路径。
求最少切割几条边使得不存在最短路径,最多能切割几条边存在最短路径。
思路:
第二问,求出一个最短路径需要的边数,然后总数减去就好了。
第一问,最小割呀!最小割=最大流。
所以给出的图肯定存在重边,然后我们要记录边值最小的边的数量,建新图所有的边都是最短路径上的边,容量就是边的数量,源点和汇点都有了,跑最大流吧。

POJ 1149

题意:【参考自 网络流建模汇总 by Edelweiss
有M个猪圈,每个猪圈里初始时有若干头猪。一开始所有猪圈都是关闭的。依次来了N个顾客,每个顾客分别会打开指定的个猪圈,从中买若干头猪。每个顾客分别都有他能够买的数量的上限。每个顾客走后,他打开的那些猪圈中的猪,都可以被任意地调换到其它开着的猪圈里,然后所有猪圈重新关上。问总共最多能卖出多少头猪。(1 <= N <= 100, 1 <= M <= 1000)
思路:
【肯定很多人第一时间直接点击链接走了吧,铃叮般得嚎啕大哭,其实窝感觉我还是讲了几个注意点的,当然自己注意到了最好了。。】

理性建图,合理分析。

建图:
美滋滋地建图;
1. 设置一个源点和一个汇点
2. 每个汇点建边,边容量就是他们最多买的pigs.
3. 最开始(固定初始状态),源点每个门建边,边容量就是the number of pigs.
4. 依次来从他们选择的门拿pigs,那么每个和他们各自的建边,边容量INF(无限)。
5. pigs会在每个人取完后开始任意转移,那么每次新建的M个点,对于打开的那些旧的门每个新的门建边,边容量INF.

建完图发现,我仿佛是给自己挖了一个坑。

按照规律,开始合并.
规律:
1. (这个规律比较好找) 对于一个点v只存在一个点u对他有流量来源,而且[u - v]容量为INF,可以并成一个.
2. 如果几个结点的流量的来源完全相同,则可以把它们合并成一个。
3. 如果几个结点的流量的去向完全相同,则可以把它们合并成一个。

对规律地盲目解读:
第一点,很好理解,不再多说。
第二点和第三点,我们只在乎一个点集V来源/去向那个点集U满足点集U点集V都存在关系(这个关系就是来源/去向).
合并也需要合理,对于这道题,除了源点和汇点,存在“门”类和“人”类点,所以我们要合并的话要往这上面靠[讲道理嘛]。对于这道题我们优先往人靠,无论是N<=100,还是感知,这是显然的。
对于合并,我们会很慌,完全不知道接下来会发生什么,这个需要做题来提高自信吧。

合并完成,总结规律,要么很明显,要么错…错了吧,回头吧…
对于这里总结规律,弱弱表示…难以总结…就…就……这样吧。
这道题就这样了。Thanks!

HDU 4292

题意:
有N(N<=200)个人,F(<=200)种食物, D(<=200)种饮料,
给出F种食物提供的次数,给出D种饮料提供的次数。
给出每个人对食物的喜欢情况, Y喜欢,N不喜欢;
给出每个人对饮料的喜欢情况, Y喜欢,N不喜欢;
问最多能使几个人使得他们都能同时得到喜欢的食物和饮料。

思路:
约束很明显啊~
确定流量应该就是人的数量。
对每个人和他喜欢的食物建边,边容量1.
对每个人和他喜欢的饮料建边,边容量1.
这两副图如何产生关系???
把人架在中间,其他放在两边建图。
但是每个人只有一个,所以拆点,然后约束人的数量,人和拆出来的人’建边,边容量1;
[其实这一步在之前就建好了] 建立源点和汇点,
源点和那些食物建边,边容量就是每种食物的个数。
汇点和那些饮料建边,边容量就是每种饮料的个数。
从源点流过来,左边符合,右边出流量为1就是符合的人数。

HDU 3572

题意:
N(<=500)个任务,M(<=200)台机器。
给出每个任务需要工作的时间,有最早才能开始做的时间Si,截止日期Ei.
A task can be interrupted and processed on different machines on different days.
在一个时刻一台机器只能执行一个任务。
问存不存在能够完成所有任务。

思路:
分析:
关键句:A task can be interrupted and processed on different machines on different days.
由于关键句条件,一个工作的工作时间可以被在规定时间任意分割,使得题目变得简单。
对于流量分析,每一天都会有M台机器在工作(假设都有工作做),那么最大工作台数和时间性质是一样的,一天能工作M台,相当于1天能工作“M天”,所以流量应该是时间
所以建图:
建立源点,源点和所有任务建边边容量为他们需要的时间
任务和他们所能工作的时间点建边边容量为1
建立汇点,和时间点建边,边容量为M
跑最大流算法,检测汇点流量是否为所需要的总天数。
PS:
[对于思路的一个小推荐]
显然最大流边容量都是代表一个“上界”,但是这道题目对于每个任务需要Pi天这是代表一个“下界”,那…只能让他做做初始流量了吧~上界=下界;

UVALive 6887

题意:[最大流求最大匹配]
N<=10000个人, M<=20000条信息
信息:A喜欢的书,B已经买了。
问能否使得每个人都可以借到书,输出YES,否则输出NO;
思路:[推荐匈牙利算法N×M]
对人进行拆点,人1,人2.
建立源点和汇点,源点与人1建边边容量为1,代表会买一个人会有一本书。
人2与汇点建边,边容量1,代表这个人有没有喜欢的书拿到。
存在关系的人建边,边容量1,借出喜欢的书。
然后跑最大流判断是否是N.

附:两份模板

/*
Lightoj 1154
*/
const int maxn = 202 + 10;
const int INF = 1000000000;

struct Edge {
  int from, to, cap, flow;
};

struct Dinic {
  int n, m, s, t;
  vector<Edge> edges;    // 边数的两倍
  vector<int> G[maxn];   // 邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
  bool vis[maxn];        // BFS使用
  int d[maxn];           // 从起点到i的距离
  int cur[maxn];         // 当前弧指针

  void ClearAll(int n) {
    for(int i = 0; i < n; i++) G[i].clear();
    edges.clear();
  }

  void ClearFlow() {
    for(int i = 0; i < edges.size(); i++) edges[i].flow = 0;
  }

  void AddEdge(int from, int to, int cap) {
    edges.push_back((Edge){from, to, cap, 0});
    edges.push_back((Edge){to, from, 0, 0});
    m = edges.size();
    G[from].push_back(m-2);
    G[to].push_back(m-1);
  }

  bool BFS() {
    memset(vis, 0, sizeof(vis));
    queue<int> Q;
    Q.push(s);
    vis[s] = 1;
    d[s] = 0;
    while(!Q.empty()) {
      int x = Q.front(); Q.pop();
      for(int i = 0; i < G[x].size(); i++) {
        Edge& e = edges[G[x][i]];
        if(!vis[e.to] && e.cap > e.flow) {
          vis[e.to] = 1;
          d[e.to] = d[x] + 1;
          Q.push(e.to);
        }
      }
    }
    return vis[t];
  }

  int DFS(int x, int a) {
    if(x == t || a == 0) return a;
    int flow = 0, f;
    for(int& i = cur[x]; i < G[x].size(); i++) {
      Edge& e = edges[G[x][i]];
      if(d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap-e.flow))) > 0) {
        e.flow += f;
        edges[G[x][i]^1].flow -= f;
        flow += f;
        a -= f;
        if(a == 0) break;
      }
    }
    return flow;
  }

  int Maxflow(int s, int t) {
    this->s = s; this->t = t;
    int flow = 0;
    while(BFS()) {
      memset(cur, 0, sizeof(cur));
      flow += DFS(s, INF);
    }
    return flow;
  }
};

struct Node{
    int x,y,num;
}p[maxn];

Dinic g;

double dis(Node a,Node b){
    return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}
vector<int> ans;

int main(){
    int t,n,total;
    double D;
    scanf("%d",&t);
    while(t--){
        scanf("%d%lf",&n,&D);
        g.ClearAll(2*n+1);
        int sorce = 0;
        total = 0;D = D*D;
        for(int i = 1;i <= n;i++){
            int x,y,ni,cap;
            scanf("%d%d%d%d",&x,&y,&ni,&cap);
            p[i].x = x;p[i].y = y;p[i].num = ni;
            total += ni;
            g.AddEdge(sorce,i,ni);
            g.AddEdge(i,i+n,cap);
        }
        for(int i = 1;i <= n;i++){
            for(int j = 1;j <= n;j++){
                if(i != j && D-dis(p[i],p[j])> 1e-6){
                    g.AddEdge(i+n,j,INF);
                }
            }
        }
        int sum = 0;
        ans.clear();
        for(int i = 1;i <= n;i++){
            g.ClearFlow();
            if(g.Maxflow(sorce,i) == total){
                ans.push_back(i);
                sum++;
            }
        }
        if(sum == 0)    printf("-1\n");
        else{
            for(int i = 0;i < ans.size()-1;i++)
                printf("%d ",ans[i]-1);
            printf("%d\n",ans[ans.size()-1]-1);
        }
    }
    return 0;
}
/*
HDU 5294
*/
#define INF 0x3f3f3f3f
#define MAXN 800047//点数的最大值
#define MAXM 2247//边数的最大值

int head[MAXM], pre[MAXM];
int dep[MAXM], cur[MAXM], gap[MAXM];//gap[x]=y :说明残留网络中dep[i]==x的个数为y
int EN;
struct Edge
{
    int to,next,cap,flow;
} edge[MAXN]; //注意是MAXM
int tol;
int k, c, m;
int s, e;//源点,汇点
int map[MAXM][MAXM];
int cost1[MAXM][MAXM], cost2[MAXM][MAXM];

int num[MAXM][MAXM];//记录边数,>1 既有重边
//加边,单向图三个参数,双向图四个参数
void addedge(int u,int v,int w,int rw = 0)
{
    edge[tol].to = v;
    edge[tol].cap = w;
    edge[tol].flow = 0;
    edge[tol].next = head[u];
    head[u] = tol++;
    edge[tol].to = u;
    edge[tol].cap = rw;
    edge[tol].flow = 0;
    edge[tol].next = head[v];
    head[v] = tol++;
}
int Q[MAXN];

void BFS(int start,int end)
{
    memset(dep,-1,sizeof(dep));
    memset(gap,0,sizeof(gap));
    gap[0] = 1;
    int front = 0, rear = 0;
    dep[end] = 0;
    Q[rear++] = end;
    while(front != rear)
    {
        int u = Q[front++];
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].to;
            if(dep[v] != -1)continue;
            Q[rear++] = v;
            dep[v] = dep[u] + 1;
            gap[dep[v]]++;
        }
    }
}
int S[MAXN];
//输入参数:起点、终点、点的总数
//点的编号没有影响,只要输入点的总数
int sap(int start,int end,int N)
{
    BFS(start,end);
    memcpy(cur,head,sizeof(head));
    int top = 0;
    int u = start;
    int ans = 0;
    while(dep[start] < N)
    {
        if(u == end)
        {
            int Min = INF;
            int inser;
            for(int i = 0; i < top; i++)
                if(Min > edge[S[i]].cap - edge[S[i]].flow)
                {
                    Min = edge[S[i]].cap - edge[S[i]].flow;
                    inser = i;
                }
            for(int i = 0; i < top; i++)
            {
                edge[S[i]].flow += Min;
                edge[S[i]^1].flow -= Min;
            }
            ans += Min;
            top = inser;
            u = edge[S[top]^1].to;
            continue;
        }
        bool flag = false;
        int v;
        for(int i = cur[u]; i != -1; i = edge[i].next)
        {
            v = edge[i].to;
            if(edge[i].cap - edge[i].flow && dep[v]+1 == dep[u])
            {
                flag = true;
                cur[u] = i;
                break;
            }
        }
        if(flag)
        {
            S[top++] = cur[u];
            u = v;
            continue;
        }
        int Min = N;
        for(int i = head[u]; i != -1; i = edge[i].next)
            if(edge[i].cap - edge[i].flow && dep[edge[i].to] < Min)
            {
                Min = dep[edge[i].to];
                cur[u] = i;
            }
        gap[dep[u]]--;
        if(!gap[dep[u]])return ans;
        dep[u] = Min + 1;
        gap[dep[u]]++;
        if(u != start)u = edge[S[--top]^1].to;
    }
    return ans;
}

void Dijkstra(int s, int n, int cost[][MAXM], int dis[MAXM])
{
    //int dis[MAXM];//记录到任意点的最短距离
    int mark[MAXM];//记录被选中的结点
    int i, j, k;
    for(i = 1; i <= n; i++)
    {
        mark[i] = 0;//初始化所有结点,每个结点都没有被选中
        dis[i] = INF;
    }
    mark[s] = 1;//start结点被选中
    dis[s] = 0;//将start结点的的距离设置为0
    int MIN;//设置最短的距离。
    for(i = 1; i <= n; i++)
    {
        k = 1;//赋初值很重要
        MIN = INF;
        for(j = 1; j <= n; j++)
        {
            if(!mark[j] && dis[j] < MIN)//未被选中的结点中,距离最短的被选中
            {
                MIN = dis[j] ;
                k = j;
            }
        }
        mark[k] = 1;//标记为被选中
        for(j = 1; j <= n; j++)
        {
            if(!mark[j] && dis[j]>dis[k] + cost[k][j])//修改剩余结点的最短距离
            {
                dis[j] = dis[k] + cost[k][j];
            }
        }
    }
}

void init()
{
    memset(head,-1,sizeof(head));
    memset(cost1,INF,sizeof(cost1));
    memset(cost2,INF,sizeof(cost2));
    memset(num,0,sizeof(num));
    EN = 0;
}

int main()
{
    int n, m;
    int u, v, w;
    int dis1[MAXM], dis2[MAXM];
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i = 1; i <= n; i++)
        {
            cost1[i][i] = 0;
        }
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            if(cost1[u][v] > w)
            {
                cost1[u][v] = w;
                cost1[v][u] = w;
                num[u][v] = 1;
                num[v][u] = 1;
            }
            else if(cost1[u][v] == w)//重边
            {
                num[u][v]++;
                num[v][u]++;
            }
        }
        Dijkstra(1,n,cost1,dis1);
        for(int i = 1; i <= n; i++)
        {
            cost2[i][i] = 0;
        }
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(i != j)
                {
                    if(dis1[i] - dis1[j] == cost1[i][j])//最短路的边
                    {
                        cost2[j][i] = 1;
                        addedge(j,i,num[i][j]);
                    }
                }
            }
        }
        Dijkstra(1,n,cost2,dis2);
        printf("%d %d\n",sap(1,n,n),m-dis2[n]);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值