网络流总结

我的网络流7题

最大流2题:

洛谷P2756 飞行员配对方案问题

分析

其实就是一个二分图匹配求最大匹配数的问题,加一个源点和汇点,再跑一遍网络流,输出方案的时候检查一下有没有流经过即可(反向边是否非0)。

注意:每个点在向源点和汇点连边时,要在输入完之后再连。否则会重复连。

#include<bits/stdc++.h>
using namespace std;
#define N 205
#define M 30005
int to[M],head[N],nex[M],w[M],lev[N],tot=1,cnt=0,inf=0x7f7f7f,s=0,t=101,n,cur[N];
struct node{ int u,v,bian; }e[M];
queue<int> q;
void add(int a,int b,int ww)
{
    tot++; to[tot]=b; w[tot]=ww; nex[tot]=head[a]; head[a]=tot;
    if(ww==0&&b&&a!=t) e[cnt].bian=tot;
}
bool bfs()
{
    memset(lev,-1,sizeof(lev));
    lev[s]=0; q.push(s);
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=nex[i])
        {
            int v=to[i];
            if(w[i]>0&&lev[v]==-1)
            lev[v]=lev[u]+1,q.push(v);
        }
    }
    if(lev[t]!=-1) return true;
    return false; 
}
int dfs(int u,int flow)
{
    if(u==t) return flow;
    int ret=flow;
    for(int i=cur[u];i!=-1;i=nex[i])
    {
        cur[u]=i;
        if(ret<=0) break;
        int v=to[i];
        if(w[i]>0&&lev[u]+1==lev[v])
        {
            int k=dfs(v,min(w[i],ret));
            ret-=k; w[i]-=k; w[i^1]+=k; 
            //异或运算:2k^1=(2k+1) (2k+1)^1=2k;
        }
    }
    return flow-ret;//最大限制 - 还剩的多少没流 =流出去了多少 
}
void dinic()
{
    int ans=0;
    while(bfs()){
        for(int i=0;i<=101;i++) cur[i]=head[i]; 
        ans+=dfs(s,inf);
    }
    printf("%d
",ans);
}
int main()
{
    int m,a,b;
    scanf("%d%d",&m,&n);
    memset(head,-1,sizeof(head));
    while(1){
        scanf("%d%d",&a,&b);
        if(a==-1&&b==-1) break;
        cnt++;
        add(a,b,inf); add(b,a,0); e[cnt].u=a; e[cnt].v=b;
    }
    //一定要在后面建,否则会重复建边 
    for(int i=1;i<=m;i++) add(s,i,1),add(i,s,0);
    for(int i=m+1;i<=n;i++) add(i,t,1),add(t,i,0);
    dinic();
    for(int i=1;i<=cnt;i++)
    if(w[e[i].bian]!=0) printf("%d %d
",e[i].u,e[i].v);
}
/*
5 10
1 7
1 8
2 6
2 9
2 10
3 7
3 8
4 7
4 8
5 10
-1 -1

2 4
1 3
1 4
2 3
-1 -1
*/
View Code

洛谷P2765 魔术球问题

«问题描述:

假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为1,2,3,...的球。

(1)每次只能在某根柱子的最上面放球。

(2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在n根柱子上最多能放多少个球。例如,在4 根柱子上最多可放11 个球。

n<=55

分析

首先,这道题的主要限制在于球(平方数),考虑对球建边。将每个球拆成两部分,第一部分对 s 连边,第二部分对 t 连边, 两部分间通过和为平方数的条件连边(边权都为1)。一个一个地加球,加一次跑一次最大流,如果在以前的基础上产生了新流,说明加入的这个球能对已有的球产生平方和的联系,即可以加在某一个球上面。加球一直到没有产生新流了,说明需要加柱子了,这时就将柱子++,一直到达到n个为止。

方案输出

每当新加一个柱子时,记录最先放入柱子的球,然后在找增广路的时候对每一个点,记录下一个可行的点是谁,类似于邻接表一样的链表式结构。然后输出的时候枚举柱子。

#include<bits/stdc++.h>
using namespace std;
#define inf 2100000000
#define M 1000005
int to[M],nex[M],head[M],w[M],tot=0,vis[M];
int nexxt[M],headd[M],lev[M],cur[M],s=M-5,t=M-4;
queue<int> q;
void add(int a,int b,int ww)
{
    to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww;
    to[++tot]=a; nex[tot]=head[b]; head[b]=tot; w[tot]=0;
}
bool bfs()
{
    memset(lev,-1,sizeof(lev));
    lev[s]=0; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=nex[i]){
            int v=to[i];
            if(lev[v]==-1 && w[i]) lev[v]=lev[u]+1,q.push(v);
        }
    }
    return lev[t]!=-1;
}
int dfs(int u,int flow)
{
    if(u==t) return flow;
    int ret=flow;
    for(int i=head[u];i!=-1;i=nex[i]){
        int v=to[i];
        if(ret<=0) break;
        if(lev[v]==lev[u]+1&&w[i]>0){
            int k=dfs(v,min(ret,w[i]));
            nexxt[u>>1]=v>>1;
            //记录一下这个球对应的下一个球是哪一个 因为存的时候*2+1了 所以要/2 不需要分奇偶讨论 
            ret-=k; w[i]-=k; w[i^1]+=k;
        }
    } 
    return flow-ret;
}
int dinic()
{
    int ans=0;
    while(bfs()){
        ans+=dfs(s,inf);
    } 
    return ans;
}
int main()
{
    int n;
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    int now=0,zhu=0;//
    headd[1]=1;
    while(zhu<=n){
        now++;
        add(s,now<<1,1); add(now<<1|1,t,1);
        for(int i=sqrt(now)+1;i*i<now*2;i++)
         add((i*i-now)<<1,now<<1|1,1);
        int tmp=dinic();
        if(!tmp) headd[++zhu]=now;
    }
    printf("%d
",now-1);
    for(int i=1;i<=n;i++)
    if(!vis[headd[i]]){
        for(int j=headd[i];j&&j!=t>>1;j=nexxt[j])
        printf("%d ",j),vis[j]=true;
        printf("
");
    }
}
View Code
#include<bits/stdc++.h>
using namespace std;
#define inf 2100000000
#define M 1000005
int to[M],nex[M],head[M],w[M],tot=0,vis[M];
int nexxt[M],headd[M],lev[M],cur[M],s=M-5,t=M-4;
queue<int> q;
void add(int a,int b,int ww)
{
    to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww;
    to[++tot]=a; nex[tot]=head[b]; head[b]=tot; w[tot]=0;
}
bool bfs()
{
    memset(lev,-1,sizeof(lev));
    lev[s]=0; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=nex[i]){
            int v=to[i];
            if(lev[v]==-1 && w[i]) lev[v]=lev[u]+1,q.push(v);
        }
    }
    return lev[t]!=-1;
}
int dfs(int u,int flow)
{
    if(u==t) return flow;
    int ret=flow;
    for(int i=head[u];i!=-1;i=nex[i]){
        int v=to[i];
        if(ret<=0) break;
        if(lev[v]==lev[u]+1&&w[i]>0){
            int k=dfs(v,min(ret,w[i]));
            nexxt[u>>1]=v>>1;
            //记录一下这个球对应的下一个球是哪一个 因为存的时候*2+1了 所以要/2 不需要分奇偶讨论 
            ret-=k; w[i]-=k; w[i^1]+=k;
        }
    } 
    return flow-ret;
}
int dinic()
{
    int ans=0;
    while(bfs()){
        ans+=dfs(s,inf);
    } 
    return ans;
}
int main()
{
    int n;
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    int now=0,zhu=0;//柱子要从0开始!!因为第一次进去时一定最大流为 0 
    headd[1]=1;
    while(zhu<=n){
        now++;
        add(s,now<<1,1); add(now<<1|1,t,1);
//now < i*i < 2*now   这样保证了i*i-now为正 且是小的数向大的数连边 
        for(int i=sqrt(now)+1;i*i<now*2;i++)
         add((i*i-now)<<1,now<<1|1,1);
        int tmp=dinic();
        if(!tmp) headd[++zhu]=now;
        //headd记录了每个柱子最下面的球是谁,然后依次跳链表遍历整个柱子里面的球 
    }
    printf("%d
",now-1);
    for(int i=1;i<=n;i++)
    if(!vis[headd[i]]){//如果这个柱子的第一个球还没有被遍历 说明这个柱子还没被遍历 
        for(int j=headd[i];j&&j!=t>>1;j=nexxt[j])
        printf("%d ",j),vis[j]=true;
        printf("
");
    }
}
有注释的

最小割2题: 

洛谷P2774方格取数问题

题意:

取了一个格子,就不能取有公共边的格子,求能取出的最大值。

分析

思路:取了一种点 就不能取另外一种点 对于这样的限制关系 可以将对立的条件进行连边
通过将边权设为inf的方式 限制不能同时选 也就是跑最大流(=最小割 )
最小割模板其实是要一部分的点向源点连边,一部分向汇点连边的形式
但是这道题我全部的点都向源汇点连了,为什么没有错呢?
是因为有一个边权的限制在里面  使其可以跑对,而下一道题骑士共存就不行了

#include<bits/stdc++.h>
using namespace std;
#define inf 2100000000
#define N 20005
#define M 1000005 
int to[M],head[N],nex[M],w[M],tot=1,cnt=0,sum=0,lev[N],n,m;
//为了不与已有的点重复 nn=n*m 注意扩展域的范围 
int id[102][102],dx[5]={0,0,1,-1},dy[5]={1,-1,0,0},s=0,t=20001,nn=10000,aa[102][102];
queue<int> q;
void add(int a,int b,int ww)
{
    to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww;
    to[++tot]=a; nex[tot]=head[b]; head[b]=tot; w[tot]=0;
}
bool bfs()
{
    memset(lev,-1,sizeof(lev));
    lev[s]=0; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=nex[i]){
            int v=to[i];
            if(lev[v]==-1&&w[i]>0) lev[v]=lev[u]+1,q.push(v);
        }
    }
    return lev[t]!=-1;
}
int dfs(int u,int flow)
{
    if(u==t) return flow;
    int ret=flow;
    for(int i=head[u];i!=-1;i=nex[i]){
        int v=to[i];
        if(ret<=0) break;
        if(lev[v]==lev[u]+1&&w[i]>0){
            int k=dfs(v,min(ret,w[i]));
            ret-=k; w[i]-=k; w[i^1]+=k;
        }
    }
    return flow-ret;
} 
 
void dinic()
{
    int ans=0;
    while(bfs()) ans+=dfs(s,inf);
    //printf("%d
",ans);
    printf("%d
",sum-ans/2);
}
int main()
{
    scanf("%d%d",&m,&n);
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++)
     for(int j=1;j<=n;j++){
         scanf("%d",&aa[i][j]);
         id[i][j]=++cnt;
         sum+=aa[i][j];
    }
    
    for(int i=1;i<=m;i++)
     for(int j=1;j<=n;j++){
         add(s,id[i][j],aa[i][j]);
         add(id[i][j]+nn,t,aa[i][j]);
         for(int k=0;k<=3;k++){
             int x=dx[k]+i,y=dy[k]+j;
            if(x<1||x>m||y>n||y<1) continue;
            add(id[i][j],id[x][y]+nn,inf);
        }
    }
    dinic();
}
View Code

洛谷P3355 骑士共存问题

 题意:

在一个位置放一个骑士,其周围有八个位置都不能放,还有障碍也不能放,求最多能放多少个骑士?

分析

与方格取数类似,但一定要一部分连接源点,一部分连汇点,不能所以的点都连源汇点
如果所有的都连了源汇点,就会发现跑出来与s t相连的边都是满流的,并没有起作用(因为边权值都为1)
一部分连,一部分不连,就可以跑出正确的最小割了
一部分与另一部分的划分 是坐标之和的奇偶性 所以也叫奇偶建图
为什么是坐标之和的奇偶呢:
oxoxo
xoxox
oxoxo
如果我们将所有点分成O和X两种点,我们可以发现同种点无法互相攻击
那我们可以把所有点分成两个点集,分别为O点的点集和X的点集,然后在两个点集有某些点对无法全选

做法:

先创一个源点和汇点

让源点到所有O点(即横纵坐标加起来为奇数的点)连一条容量为1的边

让所有X点(即横纵坐标加起来为偶数的点)到汇点连一条容量为1的边

再对无法同时选择的O点和X点,让O点连一条到X点容量为inf的点

最后跑最大流,设为最大流为x,输出n∗n−m−x就好了

(部分摘自洛谷第一篇题解

#include<bits/stdc++.h>
using namespace std;
#define inf 2100000000
#define N 80005
#define M 1000005 
int to[M],head[N],nex[M],w[M],tot=1,cnt=0,sum=0,lev[N],n,m,cur[N];
int id[202][202],dx[8]={-2,-1,-2,-1,2,1,2,1},dy[8]={1,2,-1,-2,1,2,-1,-2};
int s=0,t=40001,nn=40000,c[202][202];
queue<int> q;
void add(int a,int b,int ww)
{
    to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww;
    to[++tot]=a; nex[tot]=head[b]; head[b]=tot; w[tot]=0;
}
bool bfs()
{
    memset(lev,-1,sizeof(lev));
    for(int i=s;i<=t;i++) cur[i]=head[i];
    lev[s]=0; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=nex[i]){
            int v=to[i];
            if(lev[v]==-1&&w[i]>0) lev[v]=lev[u]+1,q.push(v);
        }
    }
    return lev[t]!=-1;
}
int dfs(int u,int flow)
{
    if(u==t) return flow;
    int ret=flow;
    for(int i=cur[u];i!=-1;i=nex[i]){
        cur[u]=i;
        int v=to[i];
        if(ret<=0) break;
        if(lev[v]==lev[u]+1&&w[i]>0){
            int k=dfs(v,min(ret,w[i]));
            ret-=k; w[i]-=k; w[i^1]+=k;
        }
    }
    return flow-ret;
} 
 
void dinic()
{
    int ans=0;
    while(bfs()) ans+=dfs(s,inf);
//    printf("%d
",ans);
    printf("%d
",n*n-m-ans);
}
int main()
{
    int a,b;
    scanf("%d%d",&n,&m);
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++) scanf("%d%d",&a,&b),c[a][b]=1;
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++){
         id[i][j]=++cnt;
         if(c[i][j]) continue;
         if((i+j)&1) add(s,id[i][j],1); //奇偶建图:奇数点在左边 
        else { add(id[i][j],t,1); continue; }
    }
    
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++){
         if(((i+j)&1)==0||c[i][j]) continue;//障碍跳过 奇偶建图:偶数点在右边 不进行连边 
         for(int k=0;k<=7;k++){
             int x=dx[k]+i,y=dy[k]+j;
             if(x<1||y<1||x>n||y>n||c[x][y]) continue;
             add(id[i][j],id[x][y],inf);
        }
    }
    dinic();
}
View Code

 

费用流2题

 洛谷P2045 方格取数加强版 

题意:现在从(1,1)出发,可以往右或者往下走,最后到达(n,n),每达到一格,把该格子的数取出来,该格子的数就变成0
这样一共走K次,现在要求K次所达到的方格的数的和最大
反向边的费用置为-w的原因:当反悔的时候 说明不获取这条边的费用 就走反向边 将原来获取的减去

建边:先拆点拆成入点x和出点x' 
x与x'连费用为格子上的数 容量为 1的边 :表示当第一次走的时候可以获取费用
x与x'再连一条费用为0 容量为 k-1的边  :表示再走k-1次将不再获取费用
x'再向下和右的入点连费用为0 容量为 k的边:表示走k次 不收获费用
注意:入点被别人连边 而出点向别人连边

#include<bits/stdc++.h>
using namespace std;
#define N 5005
#define M 1000005
int to[M],head[N],nex[M],w[M],fl[M],tot=1,flow[N],dis[N],vis[N],last[N],pre[N];//tot=1!! 
int s,t,nn=2500,max_flow=0,min_cost=0,id[55][55];
queue<int> q;
void add(int a,int b,int ww,int ff)
{
    to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww; fl[tot]=ff;
    to[++tot]=a; nex[tot]=head[b]; head[b]=tot; w[tot]=-ww; fl[tot]=0;//反向边的费用置为负的 
}
//spfa原理:在残量网络上 将费用 w 视作边权  跑最短路 
bool spfa()
{//费用流的前提都是保证最大流下再考虑费用 
//而在此题里面 流的限制使得能够恰好取k次 
    memset(vis,0,sizeof(vis));//一定要记得都要memset 
    memset(dis,0x7f7f7f,sizeof(dis));
    memset(flow,0x7f7f7f,sizeof(flow));
    dis[s]=0; vis[s]=1; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop(); vis[u]=0;
        for(int i=head[u];i!=-1;i=nex[i]){
            
            int v=to[i];
            if(fl[i]>0 && dis[v]>dis[u]+w[i]){
                dis[v]=dis[u]+w[i];
                flow[v]=min(flow[u],fl[i]);//flow[v]表示流到v这个点时 经过的流量限制是多少 
                last[v]=i;   pre[v]=u;//last记录边的编号 pre记录点的前驱 
                if(!vis[v]) q.push(v),vis[v]=1;
            }
        }
    }
    return dis[t]!=2139062143;
}
void solve()
{
    while(spfa()){
        int now=t;//从终点往回跳 跳到起点 
        max_flow+=flow[t];
        min_cost+=flow[t]*dis[t];//最小费用的定义 是容量与距离相乘 
        while(now!=s){
            fl[last[now]]-=flow[t];
            fl[last[now]^1]+=flow[t];
            now=pre[now];
        }
    }
}
int main()
{
    int x,n,k,cnt=0;
    scanf("%d%d",&n,&k);
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
      id[i][j]=++cnt;
    s=1;  t=cnt+nn;//注意这里的s t一定要设置成格子的左上方和右下方这两个点的入点和出点!! 
    //因为图中是没有额外向起点或终点连边的! 
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++){
         //printf("id:%d
",id[i][j]);
         scanf("%d",&x); x=-x;//取负数将最大费用转化成最小费用 用spfa跑最短路 
         //将点转化成边:拆点->拆成入点和出点->若要经过这个点 就必经入点与出点连接的这条边
        //第一次经过费用为原权值 多次经过则为0 经过k次的限制转化成边的容量 让网络流限制其恰好经过k次 
         add(id[i][j],id[i][j]+nn,x,1); add(id[i][j],id[i][j]+nn,0,k-1);
         if(i<n) add(id[i][j]+nn,id[i+1][j],0,k);//出边连向其它点 其它点连向入边 
         if(j<n) add(id[i][j]+nn,id[i][j+1],0,k);
    }
    solve();
    printf("%d
",-min_cost);
}
/*
3 2
1 2 3
0 2 1
1 4 2
*/
View Code

 洛谷P1251 餐巾计划问题 

关键是如何建边 ,然后跑最小费用最大流。

建边方法:将一个点视作餐厅的一天,拆成两部分:早上和晚上。  s视作纸巾的来源,t视作纸巾的去处。下面开始连边。

每天晚上花费0得到早上用过的纸巾:s -> i+n (0)

每天早上花费0用掉当天所需的纸巾:i ->t (0)

每天早上花费p重新买纸巾:s ->i (p)

每天晚上的脏纸巾可以花费快洗的费用送到后m天使用:i+n -> i+m (kf)

慢洗同理

每天用过的脏纸巾不一定今天洗,可以连向每天晚上洗:i+n -> i+n+1 (0)

#include<bits/stdc++.h>
using namespace std;
#define N 4005
#define M 1000005
#define ll long long
#define inf 2100000000
int to[M],nex[M],head[N],w[M],fl[M],vis[N],flow[N],last[N],pre[N];
int tot=1,s=0,t=4001;
ll dis[N],min_cost=0;
queue<int> q;
void add(int a,int b,int ww,int ff)
{
    to[++tot]=b; nex[tot]=head[a]; head[a]=tot; w[tot]=ww; fl[tot]=ff;
    to[++tot]=a; nex[tot]=head[b]; head[b]=tot; w[tot]=-ww; fl[tot]=0;
}
bool spfa()
{
    memset(vis,0,sizeof(vis));
    memset(dis,0x7f7f7f,sizeof(dis));//注意int的 0x7f7f7f和 long long的不一样 !! 
    memset(flow,0x7f7f7f,sizeof(flow));
    dis[s]=0; q.push(s); vis[s]=1;
    while(!q.empty()){
        int u=q.front(); q.pop(); vis[u]=0;//!!!一定要加 否则就打了个假的spfa 
        for(int i=head[u];i!=-1;i=nex[i]){
            int v=to[i];
            if(fl[i]>0 && dis[v]>dis[u]+w[i]){
                dis[v]=dis[u]+w[i];
                flow[v]=min(fl[i],flow[u]);
                last[v]=i; pre[v]=u;
                if(!vis[v]) q.push(v),vis[v]=1;
            }
        }
    }
    return dis[t]!=9187201950435737471;
}
void solve()
{
    while(spfa()){
        int now=t;
        min_cost+=dis[t]*flow[t];
        while(now!=s){
            fl[last[now]]-=flow[t];
            fl[last[now]^1]+=flow[t];
            now=pre[now];
        }
    }
}
int main()
{
    int n,p,m,mf,k,kf,x;
    scanf("%d",&n);
    memset(head,-1,sizeof(head));
    //i代表每天早上 i+n代表每天晚上 
    //用掉纸巾向 t连边 得到被s连边 
    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        add(i,t,0,x); add(s,i+n,0,x);//每天早上用掉 x张纸巾 每天晚上得到 x张脏的纸巾 
        } 
    scanf("%d%d%d%d%d",&p,&k,&kf,&m,&mf);
    for(int i=1;i<=n;i++){
        if(i+1<=n) add(i+n,i+1+n,0,inf);//留到第二天晚上洗 
        if(i+m<=n) add(i+n,i+m,mf,inf);//慢洗:今天晚上送去洗 m天后的早上得到 可以送去任意条 
        if(i+k<=n) add(i+n,i+k,kf,inf);//同理 
        add(s,i,p,inf);//每天早上可以从s那里通过p的代价重新购买 
    }
    solve();
    printf("%lld
",min_cost);
}
/*
3
1 7 5 
11 2 2 3 1

2 1 7
11 1 3 3 1
*/
View Code

我写错的地方:spfa可以重复入队,所以出队后应该清除标记!!

最小路径覆盖1题:

洛谷P2764 最小路径覆盖问题

题意:

求最小路径覆盖条数,并输出方案。

最小路径覆盖:

n个点的无向图,最多需要n条路径覆盖(每个点覆盖它自己)。但其实可以一条路径的尾节点与另一条路径的首节点相连,合并成一条路径。这样每合并一次,路径条数就会减少一条。于是就将问题转化成了:最多能合并多少条路径。然后开始建图:s -> i , i+n -> t    (1)  。然后原图中有边的相互连边,最后跑最大流即可。

ans=n-最大流

方案输出:

在dfs里面记录一个点下一个点是谁,在输出方案的时候像跳链表一样依次输出。但要注意的是每次要找到一条路径的端点(通过打vis标记),才能正确输出。

两种dfs写法不一样会导致答案也不一样??

#include<bits/stdc++.h>
using namespace std;
#define N 305
#define M 1000005
int ans=0,tot=1,n,m,s=0,t=301;
int to[M],head[N],nex[M],w[M],lev[N],cur[N],vis[N],nextt[N],fl[N];
queue<int> q; 
void add(int a,int b,int ww)
{
    to[++tot]=b; nex[tot]=head[a]; w[tot]=ww; head[a]=tot;
    to[++tot]=a; nex[tot]=head[b]; w[tot]=0;  head[b]=tot;
}
bool bfs()
{
    memset(lev,-1,sizeof(lev));
    for(int i=s;i<=t;i++) cur[i]=head[i];
    lev[s]=0; q.push(s);
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=head[u];i!=-1;i=nex[i]){
            int v=to[i];
            if(lev[v]==-1&&w[i]>0) lev[v]=lev[u]+1,q.push(v);
        }
    }
    return lev[t]!=-1;
}
int dfs( int now, int flow)
{
    if(now==t)return flow;
    for(int &i=cur[now];i;i=nex[i])
    {
        cur[now]=i;
        int qw=to[i];
        if(w[i]>0&&lev[qw]==lev[now]+1)
        {
            int kk=dfs(qw,min(flow,w[i]));
            if(kk>0)
            {
                nextt[now]=qw;//记录一个点下一个点是谁 便于方案输出 
                if(now!=s) vis[qw-n]=1;//打标记找到一条路径的起点 最后没有被标记的就是起点 
                w[i]-=kk,w[i^1]+=kk;
                return kk;
            }
        }
    }
    return 0;
}
/*int dfs(int u,int flow)另一种写法是错的。。。 
{
    if(u==t) return flow;
    int ret=flow;
    for(int i=cur[u];i!=-1;i=nex[i]){
        cur[u]=i;
        int v=to[i];
        if(ret<=0) break;
        if(w[i]>0&&lev[v]==lev[u]+1){
            int k=dfs(v,min(ret,w[i]));
            if(u!=s) vis[v-n]=true;//!!
            nextt[u]=v;
            ret-=k; w[i]-=k; w[i^1]+=k;
        }
    }
    return flow-ret;
}*/
void dinic()
{
    while(bfs()) ans+=dfs(s,2100000000);
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n;i++) add(s,i,1),add(i+n,t,1);
    int a,b;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&a,&b);
        add(a,b+n,1);
    }
    dinic();
    for(int i=1;i<=n;i++)
    if(!vis[i]){
        int now=i;
        printf("%d ",now);
        while(nextt[now]&&nextt[now]!=t){
            printf("%d ",nextt[now]-n);
            now=nextt[now]-n;
        }
        printf("
");
    } 
    printf("%d
",n-ans);
}
/*
11 9
1 2
1 3
1 4
2 5
3 6
4 7
6 9
7 10
8 11

4 3
4 1
1 2
2 3
*/
View Code
原文地址:https://www.cnblogs.com/mowanying/p/11341451.html