最大流+二分+Floyd
链接:https://ac.nowcoder.com/acm/contest/548/E
来源:牛客网Tachibana Kanade And Dream City
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 524288K,其他语言1048576K
64bit IO Format: %lld
题目描述
在 Tachibana Kanade 的幻想国度中,城市的地下通道布局大致如下:
城市里有 n 户人,每一户每天白天会产生一定量的废水,而这些废水都在晚上排出。第 i 户白天会产生废水 vi
。同时因为这是一个神奇的国度,所以每户在晚上有一定的废水处理能力 wi。
有 m 条连接户与户之间的下水道,第 i 条下水道可以双向连通,连接了第 ai 户和第 bi 户,但是由于该国的地势十分平坦,所以水流过下水道是需要时间的,水完全流过第 i 条下水道所需要的时间为 costi。
现在政府需要从一开始就确定所有水的流向(可以只流走一部分),以保证所有的废水都能被处理。所有的废水都被处理的定义是:设第 i 户人家最终拥有的废水为 v′i,则对于所有户,都有 v′i≤wi
。
等到政府确定完水的流向方案后,所有水都会开始流动,现在政府希望在 L 时间内让所有水都被处理,请告诉她 L 的最小可能长度是多少。如果不存在解,请输出 -1。
输入描述:输入的第一行为两个正整数 n,m。
接下来有 n 行,每行两个整数 vi,wi。
接下来有 m 行,每行三个整数 ai,bi,costi。
以上各值的意义如「题目描述」所示。输出描述:
输出一个整数,表示最小值,如果不可能就输出-1。
示例1
输入
复制3 2
6 2
2 4
3 7
1 2 10
2 3 20输出
复制20
说明
我们安排 1 点到 2 点流 4 个单位的水,2 点到3 点流2个单位的水,同时进行,显然时间为 20
备注:
n≤200,m≤500,1≤vi,wi≤1000,1≤costi≤109
题意:n户人家产生污水,但是各自可以处理一定量污水,有多条下水道相连,每条下水道有流过的时间,求最小的时间使得污水处理完,可认为每户人家秒处理完污水,时间是流污水的时间。
求答案的最小值,那么可以用二分,
对于check 此时要解决的是,能否在T时间内处理完污水。
这句话等同于所有污水流过的最长时间是T,
这时候要Floyd预处理各个点到各个点的距离(因为选择增广路的时候不能中转别的点,中转了就可能导致大于T,这是我们不想看见的),那么选择流过时间<=T 的路构图 ,
源点到每个点,最大流量为用户的污水量,每个点到汇点,最大流量为可以处理的污水量,
对于源点与汇点中间的图,因为不能中转别点(之前提到),所以要拆点
左边1-n,右边1-n,对于每条边,构造左边一列指向右边一列,这样就能保证跑最大流时每一条路都没有中转,
跑最大流出来,看是否最大流==∑所有用户的污水。
注意小细节,
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<climits>
#include<cmath>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
#define mod 1000000007
#define int long long
const int max_n = 550;
struct no{int to,cap,rev;}; //arc
vector<no>g[max_n]; //图
int level[max_n]; //到起点的距离
int iter[max_n]; //当前弧,在其之前的边已经没用了
int coa=0;
int mp[max_n][max_n];
int n,m;
int a[max_n],b[max_n];
void addarc(int s,int e,int c){
g[s].push_back((no){e,c,(int)g[e].size()});
g[e].push_back((no){s,0,(int)g[s].size()-1});
}
//更新层次,即level
void bfs(int s){
memset(level,-1,sizeof(level));
level[s]=0;
queue<int>q;
q.push(s);
while(!q.empty()){
int now=q.front();q.pop();
for(int i=0;i<(int)g[now].size();i++){
no &arc=g[now][i];
if(level[arc.to]!=-1||arc.cap<=0) continue;
level[arc.to]=level[now]+1;
q.push(arc.to);
}
}
}
//寻找增广路
int dfs(int v,int t,int f){
if(v==t) return f;
for(iter[v];iter[v]<(int)g[v].size();iter[v]++){
no &arc=g[v][iter[v]];
if(arc.cap<=0||level[arc.to]!=level[v]+1) continue;
int d=dfs(arc.to,t,min(f,arc.cap));
if(d>0) {
arc.cap=arc.cap-d;
g[arc.to][arc.rev].cap+=d;
return d;
}
}
return 0;
}
int Dinic(int s,int t){
int re=0;
while(1){
bfs(s);
memset(iter,0,sizeof(iter));
if(level[t]==-1) return re;
int f;
while((f=dfs(s,t,LLONG_MAX))>0)
re=re+f;
}
return re;
}
bool check(int mid){
for(int i=0;i<=2*n+1;i++) g[i].clear();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(mp[i][j]<=mid) addarc(i,j+n,LLONG_MAX);
}
}
//连接超级源点
for(int i=1;i<=n;i++) addarc(0,i,a[i]);
for(int i=1;i<=n;i++) addarc(i,i+n,b[i]);
for(int i=1;i<=n;i++) addarc(i+n,2*n+1,b[i]);
int x=Dinic(0,2*n+1);
if(x==coa) return 1;
return 0;
}
void floyd(){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
for(int k=1;k<=n;k++)
mp[j][k]=min(mp[j][k],mp[j][i]+mp[i][k]);
}
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++) {
cin>>a[i]>>b[i];
coa+=a[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) mp[i][j]=LLONG_MAX/2;
}
for(int i=1;i<=m;i++) {
int u,v,c;cin>>u>>v>>c;
mp[u][v]=min(mp[u][v],c);
mp[v][u]=min(mp[v][u],c);
}
floyd();
int ans=-1;
int l=0,r=LLONG_MAX/2-1; //不能是LLONG_MAX/2,l+r可能等于LLONG_MAX/2,mp里有许多LLONG_MAX/2
while(l<=r){
int mid=(l+r)/2;
if(check(mid)){
ans=mid;
r=mid-1;
}
else l=mid+1;
}
cout<<ans;
return 0;
}