动态网络流 SGU 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; }
补充:软件开发 , C++ ,