牛客练习赛43 Tachibana Kanade And Dream City //最大流+二分+Floyd

16 篇文章 0 订阅
10 篇文章 0 订阅

最大流+二分+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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值