题意:无向图中给出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; }