动态网络流 SGU 438

本文探讨了一种独特的问题场景,即利用最小费用流算法解决河流过河问题,考虑了游客在不同时间点跨越河流的需求。通过构建动态图模型,作者详细解释了如何在有限时间内确保所有游客安全过河,同时最小化所需时间。文章深入阐述了算法应用的逻辑和步骤,包括构建层次图、连接节点以及最终确定最优解决方案的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://acm.sgu.ru/problem.php?contest=0&problem=438

题意:

有一条东西向流淌的河,宽为 W,河中有 N 块石头,每块石头的坐标(Xi, Yi)和最大承受人数 Ci 已知。现在有 M 个游客在河的南岸,他们想穿越这条河流,但是每个人每次最远只能跳 D 米,每跳一次耗时 1 秒。问他们能否全部穿越这条河流,如果能,最少需要多长时间。 <= N <= 50, 0 < M <= 50, 0 <= D <= 1000, 0 < W(0<= 1000, 0 < Xi < 1000, 0 < Yi < W, 0 <= Ci <= 1000)。刚看完这题,想当然的认为它是一道最小费用流问题。但是当WA之后我才明白,这题并不是去求一个给定网络的最大流,而是计算这个网络随着时间推移每次能够留出多少流量。我们通过枚举时间的方式来决定在什么时刻能够把所有的人全部送到对岸。注意人是可以从河这岸的任意x坐标出发的。

(开始人都在X轴上.对岸可以看为 Y = W的一条直线)

思路;

由于本题中 人可以站在石头上不动 所以不能用最小费用流做

我们把每个时间点构建一层图    

 把同一点的前一层和后一层相连接表示在同一个点 从t秒的时候站到了t+1秒 没有动

 如果i可以通过1秒之后到达j 则第k层的i和第k+1层的j连边   k+1为我们判定的时间即我们假设的最大的时间  我们可以用二分法给它分配时间 也可以暴力一点点的加时间 如果点很多可以暴力加时间

源点汇点 怎么加边 就不用多说了 看代码注释

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int maxn=200*55;
const int inf=0x7fffffff;
struct node{
    int v,next;
    int val;
} s[maxn*200];
int level[maxn],p[maxn],que[maxn*200],out[maxn],ind;
inline void insert(int x,int y,int z){
    s[ind].v=y;
    s[ind].val=z;
    s[ind].next=p[x];
    p[x]=ind++;
    s[ind].v=x;
    s[ind].val=0;
    s[ind].next=p[y];
    p[y]=ind++;
}
void build_level(int n,int source){
    int h=0,r=0,i,u;
    for(i=0;i<=n; i++)level[i]=0;
    level[source]=1;
    que[0]=source;
    while(h<=r){
        u=que[h++];
        for(i=p[u]; i!=-1; i=s[i].next)
  {
            if(s[i].val&&level[s[i].v]==0)
            {
                que[++r]=s[i].v;
                level[s[i].v]=level[u]+1;
            }
  }
    }
}
long dinic(long n,long source,long sink){
    long ret=0,i;
    while(1){
        build_level(n,source);
        if(level[sink]==0)break;
        for(i=0; i<=n; ++i)out[i]=p[i];//有一次写错了'=',结果tle,调试了好久
        long q=-1;
        while(1){
            if(q<0){//空栈中,压入source(如果source的临接边没有满流)
                for(i=out[source];i!=-1; i=s[i].next){
                    if(s[i].val&&out[s[i].v]!=-1&&level[s[i].v]==2)break;
    }
                if(i!=-1){
                    que[++q]=i;
                    out[source]=s[i].next;
                }
                else break;
            }
            long u=s[que[q]].v;
            if(u==sink){
                long dd=inf;
                for(i=0; i<=q; i++){
                    if(dd>s[que[i]].val)dd=s[que[i]].val;
    }
                ret+=dd;
                for(i=0; i<=q; i++){
                    s[que[i]].val-=dd;
                    s[que[i]^1].val+=dd;
                }
                for(i=0; i<=q; i++){//堵塞点
                    if(s[que[i]].val==0){
                        q=i-1;
                        break;
                    }
                }
            }
            else{
                for(i=out[u]; i!=-1; i=s[i].next){
                    if(s[i].val&&out[s[i].v]!=-1&&level[u]+1==level[s[i].v])break;
    }
                if(i!=-1){
                    que[++q]=i;
                    out[u]=s[i].next;
                }
                else{//当前点没有临接的可行流
                    out[u]=-1;
                    q--;
                }
            }
        }
    }
    return ret;
}
void ini(){
    ind=0;
    memset(p,-1,sizeof(p));
}
int dis[55][55];
int pile[55][3];
int GetDis(int x1,int y1,int x2,int y2){
    int t1=x1-x2;
    int t2=y1-y2;
    return t1*t1+t2*t2;
}
void Build(int n,int m,int mid,int D,int W){
    ini();
    //0-->超级源点;
    //2*n*mid+1--->源点;
    //2*n*mid+2-->汇点;
    //超级源点到汇点的容量限制为最大m
    insert(0,2*n*mid+1,m);
    for(int k=0;k<mid;k++){
        for(int i=1;i<=n;i++){
            insert(2*k*n+i,2*k*n+n+i,pile[i][2]);//同一点的前一层和后一层相连接
            //源点和二分图的A部
            if(pile[i][1]<=D)insert(2*n*mid+1,2*k*n+i,inf);//源点到每层的第i个点连接
            //二分图的B部到汇点
            if(pile[i][1]+D>=W)insert(2*k*n+n+i,2*n*mid+2,inf);//每层的第i个点到汇点连接
        }
    }
    //二分图A部的点i可以到B部的点j 
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(dis[i][j]<=D*D){
                for(int k=0;k<mid-1;k++){
                    insert(2*k*n+n+i,2*(k+1)*n+j,inf);
                    //如果i可以通过1秒之后到达j 则第k层的i和第k+1层的j连边
                }
            }
        }
    }
}
int main(){
    int m,n,D,W;
    while (scanf("%d%d%d%d",&n,&m,&D,&W)!=EOF){
        ini();
        for(int i=1;i<=n;i++)scanf("%d%d%d",&pile[i][0],&pile[i][1],&pile[i][2]);
        if(D>=W){
            printf("1\n");
            continue;
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=i;j++){
                dis[j][i]=dis[i][j]=GetDis(pile[i][0],pile[i][1],pile[j][0],pile[j][1]);
            }
        }
        int left=1,right=m+n+1;
        int ans=m+1+n;
        while(left<=right){
            int mid=(left+right)/2;
            if(mid<=0)break;
            Build(n,m,mid-1,D,W);
            int t=dinic(2*n*(mid-1)+2,0,2*n*(mid-1)+2);
            if(t==m)right=mid-1,ans=mid;
            else left=mid+1;
        }
        if(ans<=m+n)printf("%d\n",ans);
        else printf("IMPOSSIBLE\n");
    }
    return 0;
} 


 




 

参考  http://www.cnblogs.com/Lyush/archive/2012/07/07/2580683.html

http://hi.baidu.com/aekdycoin/item/4c1a7b1c3107d3f087ad4eda

http://blog.sina.com.cn/s/blog_677a3eb30100pg2v.html

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值