• 网络流总结


    我的网络流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
  • 相关阅读:
    编程的发展史及Python简介
    计算机的主要部件和组成
    编程二连
    数据库中的数据类型
    数据库概念和基本操作(mysql的操作)
    并发编程之协程
    并发编程之多线程
    网络编程之socket编程
    并发编程之多进程
    网络编程之网络协议
  • 原文地址:https://www.cnblogs.com/mowanying/p/11341451.html
Copyright © 2020-2023  润新知