poj 2391 最大流+floyd+二分(转移避难)

题意:无向图中给出n个点,m条边,每个点给定两个值a和b,a表示当前在该点的人数,b表示该点的人数限制。之后给定m条边以及边上的权值。问是否所有人都能通过转移找到避难处,如果能,输出最短的移动距离。

思路:先用floyd求出任意两点之间的最短距离,然后二分答案。对每个二分值建立网络流进行求解(整个思路类似2112挤奶那道题)。建图时要拆点。对每个点x,拆成x'和x'',然后x'和x''之间有一条无限容量的边,这样的话,随便多少牛路过这个点都是可以的,如果i->j这条边符合了距离限制,就加i'->j'' 所有的边加完后,建立源点,对所有的x'连边,容量为已经有的牛,汇点的话,就用所有的j''连接汇点,容量就是能容纳的牛的数量。

最大流用dinic的递归写法,还是比用栈写简单不少。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstdlib>
using namespace std;
#define clc(s,t) memset(s,t,sizeof(s))
#define INF 0x3fffffffffffffff
#define N 205
int a[N],b[N];
long long dis[N][N],low,high,mid;
int n,m;
int f[N*2],first[N*2],top,d[N*2];
struct edge{
    int y,c,next;
}e[2*(N*N+2*N+5)];
void init(){
    int i,j;
    for(i = 1;i<=n;i++)
        for(j = 1;j<=n;j++)
            dis[i][j] = INF;
    low = high = 0;
}
void add(int x,int y,int c){
    e[top].y = y;
    e[top].c = c;
    e[top].next = first[x];
    first[x] = top++;
}
void floyd(){
    int i,j,k;
    for(k = 1;k<=n;k++)
        for(i = 1;i<=n;i++)
            for(j = 1;j<=n;j++){
                dis[i][j] = min(dis[i][j],dis[i][k]+dis[k][j]);
                if(k==n && dis[i][j] != INF)
                    high = max(high,dis[i][j]);
            }
}
void create(long long x){
    int i,j;
    clc(first,-1);
    top = 0;
    for(i = 1;i<=n;i++){
        add(0,i,a[i]);
        add(i,0,0);
        add(n+i,2*n+1,b[i]);
        add(2*n+1,n+i,0);
        add(i,n+i,1005);
        add(n+i,i,0);
    }
    for(i = 1;i<=n;i++)
        for(j = 1;j<=n;j++)
            if(dis[i][j] <= x){
                add(i,n+j,1005);
                add(n+j,i,0);
            }
}
int bfs(int s,int t){
    int i,now;
    clc(d, -1);
    queue<int>q;
    q.push(s);
    d[s] = 0;
    while(!q.empty()){
        now = q.front();
        q.pop();
        for(i = first[now];i!=-1;i=e[i].next)
            if(-1 == d[e[i].y] && e[i].c>0){
                d[e[i].y] = d[now]+1;
                if(e[i].y == t)
                    return 1;
                q.push(e[i].y);
            }
    }
    return 0;
}
int dfs(int now,int a){
    int i,j,res=0;
    if(now == 2*n+1 || a==0)
        return a;
    for(i = f[now];i!=-1;i = f[now] = e[i].next)
        if(d[e[i].y] == d[now]+1 && (j = (dfs(e[i].y,min(a,e[i].c))))>0){
            e[i].c -= j;
            e[i^1].c += j;
            res += j;
            a -= j;
            if(!a)
                break;
        }
    return res;
}
int dinic(int s,int t){
    int res=0;
    while(bfs(s,t)){
        memcpy(f, first, sizeof(first));
        res += dfs(s,1005);
    }
    return res;
}
int main(){
    int i,j,x,y,sum,flag;
    long long w;
    while(scanf("%d %d",&n,&m)!=EOF){
        init();
        flag = sum = 0;
        for(i = 1;i<=n;i++){
            scanf("%d %d",&a[i],&b[i]);
            sum += a[i];
        }
        for(i = 1;i<=m;i++){
            scanf("%d %d %lld",&x,&y,&w);
            dis[x][y] = min(dis[x][y],w);
            dis[y][x] = dis[x][y];
        }
        floyd();
        high++;//没加这句贡献了一次WA,说明答案可能正是最长的路径值
        while(low < high){
            mid = (low+high)>>1;
            create(mid);
            j = dinic(0,2*n+1);
            if(j==sum){
                high = mid;
                flag = 1;
            }
            else
                low = mid+1;
        }
        if(flag)
            printf("%lld\n",low);
        else
            printf("-1\n");
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值