网络流二十四题

1/搭配飞行员:

给n个飞行员,有m个为副驾驶,其他为主驾驶。

每个飞机需要一个副驾驶和一个主驾驶,给出可以一起合作的组合,问最多可以让几架飞机起飞。

建立两列点,一列为主驾驶一列为副驾驶,建立s与t,s向每个主驾驶连一条容量为1(每个主驾驶的人只能用一次),副驾驶连到t容量为1(每个副驾驶只能要一次),然后主驾驶与副驾驶合坐连一条容量为1的边,最大流就是最大合作的数量(二分图匹配应该也可以过)。

2/太空飞行计划:

有n个实验和m个仪器,每个实验获得不同的价值,每个仪器需要一定的购买费用,一些实验会使用一些仪器,问最大获利。

显然最大权闭合子图模型(S连向所有正权值节点,容量为价值,所有负权值节点连向T,容量为价值,原来的边为INF)。

3/最小路径覆盖:

路径覆盖就是在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每个顶点一次且仅一次)

1.一个单独的顶点是一条路径;

2.如果存在一路径p1,p2,......pk,其中p1 为起点,pk为终点,那么在覆盖图中,顶点p1,p2,......pk不再与其它的顶点之间存在有向边.

对于一个路径覆盖,有如下性质:

1、每个顶点属于且只属于一个路径。

2、路径上除终点外,从每个顶点出发只有一条边指向路径上的另一顶点。

算法:把原图的每个点V拆成VxVx和VyVy两个点,如果有一条有向边A->B,那么就加边Ax−>ByAx−>By。这样就得到了一个二分图。那么最小路径覆盖=原图的结点数-新图的最大匹配数。

证明:一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。

4/魔术球:

给n个铁柱,问最多可以放几个球,球的编号一次为1、2、3、4、5,要求按编号依次放入,每个球必须与其相邻的球相加的编号为完全平方数,否则它应该单独在一个铁柱上,问n个铁柱最多可以放几个球。

错误的思路:刚开始一直想着如何通过费用流的方式去限制编号小的铁柱必定放了,其实这里可以把问题转换为判定形问题。

给定n个球,每个球向后面可以放的编号连一条边,那么可以得知这n个球需要的铁柱为最小路径覆盖(每次找一个点作为起点,两两路径无交集,求最少需要的路径数)。枚举球,第一次需要n+1个铁柱的方案-1即为答案。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+7;
bool vis[maxn];
int pre[maxn],head[maxn],tot;
struct E{
    int next,to;
}e[maxn+maxn];  //链式前向星要开双倍的大小
void add(int u,int v){ //如果重复使用n+1,n+2个点建立边,记得把head[n+1]=-1
    tot++;
    e[tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
}
int to[maxn],N;
int bfs(int x){//最后pre[i]=x,表示i被X连接
    for(int i=head[x];i!=-1;i=e[i].next){
        int v=e[i].to;
        if(vis[v]==0){ //这里每次在循环中都需要清空一次
            vis[v]=1;
        if(pre[v]==-1||bfs(pre[v])){
            to[x]=v-N;
            pre[v]=x; //X之前连接点为out[x][i]
            return 1;//这里点目的是求出最大匹配的数量
        }
    }
    }
    return 0;
}
int main(){
    int i,j,k,f1,f2,f3,f4,t1,t2,t3,t4,t5,n,m;
    //freopen("in.txt","r",stdin);
    int M;
    scanf("%d",&M);
    int u,v;
    n=3000;
    N=n;
    tot=0;memset(head,-1,sizeof(head));
    memset(pre,-1,sizeof(pre));
    f1=0;
    for(i=1;i<=n;i++)
        to[i]=i;
    for(i=1;;i++){
        for(j=1;j<i;j++){
            t1=sqrt(j+i);
            if(t1*t1==j+i){
                add(j,i+n);
            }
        }
        t1=0;
        for(j=1;j<=i;j++){
            if(to[j]==j){
                memset(vis,0,sizeof(vis));
                t1+=bfs(j);
            }
        }
        f1+=t1;
        if(i-f1>M){
            cout << i-1<<endl;
            memset(vis,0,sizeof(vis));
            for(j=1;j<i;j++){
                if(vis[j]==0){
                    vis[j]=1;cout <<j;
                    if(j==to[j]){
                        cout <<endl;continue;
                    }
                    for(k=to[j];;k=to[k]){
                        cout <<" "<<k;
                        vis[k]=1;
                        if(k==to[k])break;
                    }
                    cout<<endl;
                }
            }
            return 0;
        }
    }
    return 0;
}

5/圆桌聚餐:

给定n种人,每种人ai个,有m个桌子,每个桌子有bi个,要求同一种人不能在同一个桌子上,问是否可以把所有人坐满。

显然最大流,s连人,容量为人的数量,桌子连t,容量为桌子的数量,然后每个人连到一个桌子容量为1,最大流=人数说明可以坐满。然后方案就是对每个人枚举每一条容量,当容量为0的时候表明这个人坐到了这一个桌子上。
6/方格取数:

显然最大点权独立集,转换为二分图就是总点权-最小点覆盖集

7/餐巾计划:

有n天,每天需要A[i]块餐巾,餐巾可以从商店获取新的或者通过把旧餐巾送到快洗店和慢洗店获得,每天干净餐巾不少于A[i],问最少的花费。

这里有个必须最大流送到tp,而且还能重新把这些用过的流量使用,就可以拆点,从sp到每个点的复制点容量就为A[i],而费用为0。模型:sp连到1-n个点,容量inf,费用为新餐巾的费用,i+n->i+n+M/i+n+N,费用为送到洗衣店各自的费用,这里每个点只放一次,然后每天的新毛巾可继承,就不用把后面的点都连接了。i->i+1连一个费用为0的可继承点。

为啥我的板子貌似有一丢丢慢。

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#include <queue>
#define V 101000
#define E 1000100
#define inf 9999999
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
int vis[V],dist[V],pre[V];//V为点的数量
struct Edge{
    int u,v,c,cost,next;
}edge[E];
int head[V],cnt,sp,tp;
void init(){
    cnt=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int c,int cost){
    edge[cnt].u=u;edge[cnt].v=v;edge[cnt].cost=cost;
    edge[cnt].c=c;edge[cnt].next=head[u];head[u]=cnt++;
    edge[cnt].u=v;edge[cnt].v=u;edge[cnt].cost=-cost;
    edge[cnt].c=0;edge[cnt].next=head[v];head[v]=cnt++;
}

bool spfa(int begin,int end){
    int u,v;
    queue<int> q;
    for(int i=0;i<=end+2;i++){
        pre[i]=-1;
        vis[i]=0;
        dist[i]=inf;
    }
    vis[begin]=1;
    dist[begin]=0;
    q.push(begin);
    while(!q.empty()){
        u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i!=-1;i=edge[i].next){
            if(edge[i].c>0){
                v=edge[i].v;
                if(dist[v]>dist[u]+edge[i].cost){
                    dist[v]=dist[u]+edge[i].cost;
                    pre[v]=i;
                    if(!vis[v]){
                        vis[v]=true;
                        q.push(v);
                    }
                }
            }
        }
    }
    return dist[end]!=inf;
}
int MCMF(int begin,int end){
    int ans=0,flow;
    int flow_sum=0;
    while(spfa(begin,end)){
        flow=inf;
        for(int i=pre[end];i!=-1;i=pre[edge[i].u])
            if(edge[i].c<flow) //期间最大的流量
                flow=edge[i].c;
        for(int i=pre[end];i!=-1;i=pre[edge[i].u]){
            edge[i].c-=flow;
            edge[i^1].c+=flow;
        }
        ans+=dist[end]*flow;//为每次流量和费用的乘机
        flow_sum += flow; //flow_sum为最大流
    }
    //cout << flow_sum << endl;
    return ans;

}
int A[maxn];
int main(){
    //freopen("in.txt","r",stdin);
    int n,m,a,b,c,t1,t2,t3,i,j,k,f1,f2,f3,f4;
    int P,M,F,N,S;
    scanf("%d %d %d %d %d %d",&n,&P,&M,&F,&N,&S);
    init();
    sp=0;tp=n+n+1;
    for(i=1;i<=n;i++){
        scanf("%d",&A[i]);
    }
    addedge(sp,1,inf,P);
    for(i=1;i<=n;i++){
        addedge(sp,i+n,A[i],0);
        addedge(i,tp,A[i],0);
        if(i!=n)
        addedge(i,i+1,inf,0);
    }
    for(i=1;i<=n;i++){
        if(i+M<=n)
        addedge(i+n,i+M,A[i],F);
        if(i+N<=n)
        addedge(i+n,i+N,A[i],S);
    }
    printf("%d\n",MCMF(sp,tp));
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值