网络流习题

一、POJ 2391    【拆点+二分+最大流】

【题目大意】
给定一个无向图,点 i 处有 Ai 头牛,点 i 处的牛棚能容纳 Bi 头牛,求一个最短时
间 T 使得在 T 时间内所有的牛都能进到某一牛棚里去。 (1 <= N <= 200, 1 <= M <=
1500, 0 <= Ai <= 1000, 0 <= Bi <= 1000, 1 <= Dij <= 1,000,000,000)

【建模方法】
将每个点 i 拆成两个点 i’, i’’,连边(s, i’, Ai), (i’’, t, Bi)。二分最短时间 T,若 d[i][j]<=T
(d[i][j]表示点 i, j 之间的最短距离)则加边(i’, j’’, ∞)。每次根据最大流调整二分
的上下界即可。
一种错误的建图方法,即不拆点,见下图:
其中每条无向边表示两条方向相反的有向边,容量均为∞。

当二分到 T = 70 的时候,显然我们只加入了(2, 3)和(3, 4)两条无向边,因为只有
这两对点间的最短距离小于等于 70。但是从图中也可以看出,由于没有拆点,
点 2 也可以通过这两条边到达点 4,而实际上这是不允许的。也就是说我们所加
的限制条件没有起到作用。由此可见,只有拆点才是正确的做法。

本题注意longlong


//#include<bits/stdc++.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <queue>
#include <stack>
#include <set>
#include <algorithm>

using namespace std;

#define For(i,a,b) for(int (i)=(a);(i) < (b);(i)++)
#define rof(i,a,b) for(int (i)=(a);(i) > (b);(i)--)
#define IOS ios::sync_with_stdio(false)
#define lson l,m,rt <<1
#define rson m+1,r,rt<<1|1
#define mem(a,b) memset(a,b,sizeof(a))

typedef long long ll;
typedef unsigned long long ull;


void RI (int& x){
    x = 0;
    char c = getchar ();
    while (c == ' '||c == '\n')    c = getchar ();
    bool flag = 1;
    if (c == '-'){
        flag = 0;
        c = getchar ();
    }
    while (c >= '0' && c <= '9'){
        x = x * 10 + c - '0';
        c = getchar ();
    }
    if (!flag)    x = -x;
}
void RII (int& x, int& y){RI (x), RI (y);}
void RIII (int& x, int& y, int& z){RI (x), RI (y), RI (z);}

/**************************************END define***************************************/
const int maxn = 510;
const int maxm = 1e5;
const int INF =0x3f3f3f3f;


int start,end,cnt;
int dis[maxn],gap[maxn];
ll floy[210][210];
int n,m;
int top,node[maxn];
int A[maxn],B[maxn];
ll total;
struct  Side
{
    int u,to,next;
    ll c;
}side[maxm];

void add_side(int u,int v,ll c)
{
    side[top]=(Side){u,v,node[u],c}; node[u]=top++;
    side[top]=(Side){v,u,node[v],0}; node[v]=top++;
}

void Floyd()
{
    For(k,1,n+1)For(i,1,n+1)For(j,1,n+1){
        if(floy[i][j]>floy[i][k]+floy[k][j])
            floy[i][j]=floy[i][k]+floy[k][j];
    }
}
ll get_flow(int u,ll flow)
{
    if(u==end) return flow;
    ll ans=0;
    for(int i=node[u];i!=-1;i=side[i].next){
        int v=side[i].to;
        ll c=side[i].c;
        if(dis[u]>dis[v]&&c){
            ll f=get_flow(v,min(flow-ans,c));
            ans+=f;
            side[i].c-=f;
            side[i^1].c+=f;
            if(ans==flow) return ans;
        }
    }
    if(!--gap[dis[u]]) dis[start]=cnt+2;
    gap[++dis[u]]++;
    return ans;
}
ll solve(ll k)
{
    start=0,end=n+n+1;
    cnt=end+1;
    top=0;
    mem(node,-1);
    For(i,1,n+1) 
        add_side(start,i,A[i]),
        add_side(i+n,end,B[i]),
        add_side(i,n+i,1e15);
    For(i,1,n+1){
        for(int j=i+1;j<=n;j++){
            if(floy[i][j]<=k)
                add_side(i,j+n,1e15),add_side(j,i+n,1e15);
        }
    }
    ll ans=0;
    mem(gap,0);mem(dis,0);
    gap[0]=cnt;
    while(dis[start]<cnt) ans+=get_flow(start,1e15);
    return ans;
}

int main()
{
    //freopen("input.txt","r",stdin);
    while(~scanf("%d%d",&n,&m)){
        total=0;
        For(i,1,n+1) {RII(A[i],B[i]);total+=A[i];}
        For(i,0,210)
        For(j,0,210)
            if(i==j)floy[i][i]=0;
            else floy[i][j]=1e15;
        For(i,0,m){
            int u,v,w;
            RIII(u,v,w);
            if(floy[u][v]>w) floy[u][v]=floy[v][u]=w;
        }
        Floyd();
        ll x=0,y=0;
        For(i,1,n+1)For(j,1,n+1)
            if(floy[i][j]!=1e15) y=max(y,floy[i][j]);
        if(solve(y+1)<total) {
            puts("-1");
            continue;
        }
        while(x<y){
            ll mid=(y+x)/2;
            if(solve(mid)>=total) y=mid;
            else x=mid+1;
        }
        printf("%lld\n",x );
    }
    return 0;
}



二、POJ 3057  【二分图匹配+二分】

队友题解 http://blog.csdn.net/houxinssdut/article/details/48051703

一侧是点编号,一侧是门,

且把一个门拆成1~左侧点数目个,枚举时间T,若u点到某门距离dis 小于等于T ,建边<u,d+dis>....<u,d+T>

最大匹配数即可通过的人数

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <queue>
#include <stack>
#include <set>
#include <algorithm>

using namespace std;

#define For(i,a,b) for(int (i)=(a);(i) < (b);(i)++)
#define rof(i,a,b) for(int (i)=(a);(i) > (b);(i)--)
#define IOS ios::sync_with_stdio(false)
#define lson l,m,rt <<1
#define rson m+1,r,rt<<1|1
#define mem(a,b) memset(a,b,sizeof(a))

typedef long long ll;
typedef unsigned long long ull;
const int maxn = 3e4;
const int maxm = 4e6;
const int INF =0x3f3f3f3f;
int gnum[15][15];
int sdis[110][50];//i->j  zuiduanlu
int px[4]={1,0,-1,0};
int py[4]={0,1,0,-1};//down right up left
char g[15][15];
int n,m;
int A,B;
int posd[maxn];

struct Side{
    int from,to,next;
};
struct BPM{
   int n,m;    //左右n,m个点
   Side side[maxm];
   int top;
   int node[maxn];
   int left[maxn];       // left[i]为右边第i个点的匹配点编号,-1表示不存在
   bool T[maxn];           // T[i]为右边第i个点是否已标记
   void init(int nn,int mm)
   {
       n=nn,m=mm;
       top=0;
       mem(node,-1);
   }
   void add_side(int u,int v)
   {
       side[top]=(Side){u,v,node[u]};  node[u]=top++;
   }
   bool dfs(int u){
       for(int i=node[u];i!=-1;i=side[i].next){
           int v=side[i].to;
           if(!T[v]){
               T[v]=true;
               if(left[v]==-1||dfs(left[v])){
                   left[v]=u;
                   return true;
               }
           }
       }
       return false;
   }
   int hungarian()
   {
       int ans=0;
       mem(left,-1);
        for(int u=0;u<n;u++){
            mem(T,0);
            if(dfs(u)) ans++;
        }
        return ans;
    }
};
BPM solver;

int dis[maxn],vis[15][15];
int diss[15][15];
struct Point{
    int x,y;
};
vector <Point> vd, vp;
queue <Point> q;
void bfs(int x,int y,int did)
{
    mem(vis,0);
    mem(diss,0);
    while(!q.empty())q.pop();
    Point p;
    p.x=x,p.y=y;
    q.push(p);
    while(!q.empty()){
        p=q.front();
        q.pop();
        for(int i=0;i<4;i++){
            int nx=p.x+px[i],ny=p.y+py[i];
            if(nx>0&&nx<n-1&&ny>0&&ny<m-1)
                if(g[nx][ny]=='.'&&!vis[nx][ny]){
                    vis[nx][ny]=1;
                    int d=diss[nx][ny]=diss[p.x][p.y]+1;
                    q.push((Point){nx,ny});
                    sdis[gnum[nx][ny]][did]=d;
                }
        }
    }
}
int solve(int k)
{
    solver.init(vp.size(),vd.size()*k);
    for(int i=1;i<=vp.size();i++){
        for(int j=0;j<vd.size();j++){
            if(sdis[i][j]<=k){
                for(int w=sdis[i][j];w<=k;w++){
                    solver.add_side(i-1,j*k+w-1);
                    //cout<<i-1<<" "<<j*k+w-1<<"--\n";
                }
            }
        }
    }
   /* cout<<k<<endl;
    for(int i=0;i<solver.top;i++){
            printf("%d %d \n",solver.side[i].from,solver.side[i].to );
    }*/
    return solver.hungarian();
}
int main(){
    //freopen("test.txt","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;i++){
            scanf("%s",g[i]);
        }
        mem(sdis,0x3f);
        mem(gnum,-1);
        vd.clear(), vp.clear();// 存 ‘D’和‘.’坐标
        For(i,0,n)
        For(j,0,m){
            if(g[i][j]=='D')
                vd.push_back((Point){i,j});
            else if(g[i][j]=='.'){
                vp.push_back((Point){i,j});
                gnum[i][j]=vp.size();//‘.’的编号
            }
        }
        A=vp.size();// A是'.'数目
        for(int i=0; i<vd.size();i++){
            bfs(vd[i].x,vd[i].y,i);
        }
        if(vp.size()==0){
            printf("0\n");
            continue;
        }
        int impo=1;
        for(int i=1;i<=A;i++){
            impo=1;
            for(int j=0;j<vd.size();j++){
                if(sdis[i][j]!=INF) {
                    impo=0;
                    break;
                }
            }
            if(impo) break;
        }
        if(impo){
            printf("impossible\n");
            continue;
        }
        int x=0,y=n*m;
        while(x<y){
            int mid=(y+x)/2;
            if(solve(mid)>=A) y=mid;
            else x=mid+1;
        }
        printf("%d\n",x );
    }
    return 0;
}



三、POJ2699

《POJ 2699 The Maximum Number of Strong Kings 》
【题目大意】
一场联赛可以表示成一个完全图,点表示参赛选手,任意两点 u, v 之间有且仅有
一条有向边(u, v)或(v, u),表示 u 打败 v 或 v 打败 u。一个选手的得分等于被他打
败的选手总数。一个选手被称为“strong king”当且仅当他打败了所有比他分高
的选手。分数最高的选手也是 strong king。现在给出某场联赛所有选手的得分序
列,由低到高,问合理安排每场比赛的结果后最多能有几个 strong king。已知选
手总数不超过 10 个。

【思路】10个人,至多有10*9/2=45场比赛,可以把每场比赛设为一个点,设为match[u][v],

想到枚举strong king的个数,判断满足满流条件的最大值。当strong king个数为C时,如果存在满流的情况,那么一定存在

得分最高的C个是strong king的情况(贪心很好想,每个人只有赢的数目有差异,其他都是相同的,赢的场次越多,越有可能得分最高)。当对于分数最低的选手,比他的分数不足以达到C,那么C不满足条件,枚举退出。


于是乎可以建图。流量为 比赛场次=得分数

1、源点到每场比赛有一条容量为1的边,每场比赛最多贡献一个积分

2、对于后C个选手,每个人都要赢所有比他分数高的人,对应的比赛向他连一条边,容量为1,这是必须要走的边。

其他的情况,每场比赛输赢没有强制要求,分别向两个参加比赛的选手建立容量为1 的边,这是可选择的边。

(PS:因为存在必须要走的边,出于严谨性要把该选手拆成两个点:i,i',必须要走的边连在i'上,不必须走的边连在i上,i到i'有一条容量为【【选手赢的场次】-【必须赢的场次】】的边。 但是因为这个模型考虑的是是否满流,每场比赛的入流量为1,每场比赛必须走才能保证满流,而对于输赢定下来的比赛只有一条向外流的边,所以可以不进行拆点限制。【本题特殊,有强制性的流量要求时,其他题要注意拆点】

3、每个选手向汇点连边,容量为得分。


此处应贴代码:代码木有



四、《POJ 3281 Dining 》

【题目大意】
有 F 种食物和 D 种饮料, 每种食物或饮料只能供一头牛享用, 且每头牛只享用一
种食物和一种饮料。现在有 N 头牛,每头牛都有自己喜欢的食物种类列表和饮
料种类列表,问最多能使几头牛同时享用到自己喜欢的食物和饮料 。(1 <= F <=
100, 1 <= D <= 100, 1 <= N <= 100)

最大流的正确性依赖于它的每一条 s-t 流都与一种实际方案一一对
应。那么此题也需要用 s-t 流将一头牛和它喜欢的食物和饮料“串”起来,而食
物和饮料之间没有直接的关系,自然就想到把牛放在中间,两边是食物和饮料,
由 s, t 将它们串起来构成一种分配方案。至此建模的方法也就很明显了:每种食
物 i 作为一个点并连边(s, i, 1),每种饮料 j 作为一个点并连边(j, t, 1),将每头牛 k
拆成两个点 k’, k’’并连边(k’, k’’, 1), (i, k’, 1), (k’’, j, 1),其中 i, j 均是牛 k 喜欢的食物
或饮料。求一次最大流即为结果。

五、JOJ 2453 CANDY

【题目大意】
有 N 颗糖果和 M 个小孩,老师现在要把这 N 颗糖分给这 M 个小孩。每个小孩 i
对每颗糖 j 都有一个偏爱度 Aij,如果他喜欢这颗糖,Aij = 2,否则 Aij = 1。小孩 i
觉得高兴当且仅当∑Cij×Aij >= Bi,j=1,2,…,N,若他分得了糖 j,Cij = 1,否则 Cij =
0。 问能否合理分配这 N 颗糖, 使得每个小孩都觉得高兴。 (1 <= N <= 100,000, 1 <=
M <= 10, 0 <= Bi <= 1,000,000,000)

【方法】一种最直观的想法就是每颗糖 i 作为一个点并连边(s, i, ?),每个小孩 j 作为一个
点并连边(j, t, Bj)。若小孩 j 喜欢糖果 i 则连边(i, j, 2),否则连边(i, j, 1),然后求一
次最大流看是否等于∑Bj。但是问题也很明显,我们还没有给与源点关联的边确
定容量。实际上我们无法确定它们的容量,因为最大流无法实现这样一种控制:
一个点有若干条出边,容量不尽相同,现在要求经过该点的流可以任选一条出边
流走,且一旦选定之后就只能从这条边流而不能再进入其他的出边。
因此我们无
法确定与源关联的边的容量,因为经过每颗糖 i 的流无法在出边容量有 1 又有 2
的情况下作出正确的选择。
那么是否就没有办法了呢?虽然流无法在容量有1又有2的情况下作出正确的选
择, 但却可以在容量有 1 又有 0 的情况下最自然地作出正确的选择, 流过去就表
示选择了那条出边,且因为容量为 1,不会再有流进入其他的出边。
那么此题的
构图方法也就出来了:每颗糖 i 作为一个点并连边(s, i, 1),每个小孩 j 作为一个
点并连边(j, t, floor(Bj/2)),若小孩 j 喜欢糖果 i 则连边(i, j, 1),否则连边(i, j, 0)或者
干脆不连边,效果一样。设最大流为 ans,若 ans+N >= ∑Bj 则可以满足要求。
为什么?因为每颗糖迟早都要分给某个小孩,它一定会为总权值贡献 1,只不过
如果它分给了喜欢它的小孩就再额外贡献 1。现在我只考虑这额外的 1 单位贡献
究竟能送出去多少,最后加上基值 N 并与∑Bj 比较即可。


六、最大权闭合子图

http://www.cnblogs.com/kane0526/archive/2013/04/05/3001557.html



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值