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