[网络流24题(1/24)] 最小路径覆盖问题(洛谷P2764)

传送门

分析:

本题的结论是:最小路径覆盖=顶点数-原图的最大匹配数

证明(逼逼)

对于 1 1 1对由 1 1 1条边覆盖的点而言, 点 数 − 1 = 边 数 点数-1=边数 1=

倘若对于 n n n个由 m m m边所覆盖的点而言,则等价于把上述式子归纳,则有: n − m = c o v e r n-m=cover nm=cover

而现在我们需要令 c o v e r cover cover最大化,显然我们必定是要取到尽可能多的符合题意的边数。

而在题目中,存在着一个点只能被一条边所覆盖,因此,这代表着,对于一个点,只能够有一条入边,同时也只能有一条出边。

考虑到这个限制,我们考虑将一个点拆成两个点,其中一个点代表入点,另一个点代表出点,根据原图的关系 u → v u\to v uv,将当前 u u u的出点连向下一个点 v v v的入点,且边的容量为 1 1 1

而这样建出来的图,则可以满足一个点只能被一条边入,一条边出的限制。

之后我们发现,我们现在所连出来的的一张图是二分图,故我们可以直接建立超级源点和超级汇点,并在这张图上跑最大流即可以求出最大的边数 m m m。因此最终的答案即为 n − m n-m nm

同时这个题中需要我们求出路径数。而对于这个,我们可以通过这张图的残量网络去求解。我们遍历每一个点,如果发现某一个点的当前容量为 0 0 0,则不断延申这条边直到终点。而又因为在这张图中每条边的容量上限为 1 1 1,故一条全部为 0 0 0的路径必定为答案。而因为这样的过程中,最多遍历每个点 1 1 1次,故时间复杂度为 O ( n ) \mathcal{O(n)} O(n)

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1005;
const int maxm=10005;
const int inf=0x3f3f3f3f;
struct Node{
    int to,val,next;
}q[maxm<<1];
int head[maxn],cnt=0,dep[maxn],cur[maxn],vis[maxn];
int tag[maxn];
int sp,ep,maxflow;
void init(){
    memset(head,-1,sizeof(head));
    cnt=2,maxflow=0;
}
void add_edge(int from,int to,int val){
    q[cnt].to=to;
    q[cnt].val=val;
    q[cnt].next=head[from];
    head[from]=cnt++;

    q[cnt].to=from;
    q[cnt].val=0;
    q[cnt].next=head[to];
    head[to]=cnt++;
}
bool bfs(int n){
    for(int i=0;i<=n;i++){
        cur[i]=head[i],dep[i]=0x3f3f3f3f;
        vis[i]=0;
    }
    dep[sp]=0;
    queue<int>que;
    que.push(sp);
    while(!que.empty()){
        int x=que.front();
        que.pop();
        vis[x]=0;
        for(int i=head[x];i!=-1;i=q[i].next){
            int to=q[i].to;
            if(dep[to]>dep[x]+1&&q[i].val){
                dep[to]=dep[x]+1;
                if(!vis[to]){
                    que.push(to);
                    vis[to]=1;
                }
            }
        }
    }
    if(dep[ep]!=inf) return true;
    else return false;
}
int dfs(int x,int flow){
    int rlow=0;
    if(x==ep){
        maxflow+=flow;
        return flow;
    }
    int used=0;
    for(int i=cur[x];i!=-1;i=q[i].next){
        cur[x]=i;
        int to=q[i].to;
        if(q[i].val&&dep[to]==dep[x]+1){
            if(rlow=dfs(to,min(flow-used,q[i].val))){
                used+=rlow;
                q[i].val-=rlow;
                q[i^1].val+=rlow;
                if(used==flow) break;
            }
        }
    }
    return used;
}
int dinic(int n){
    while(bfs(n)){
        dfs(sp,inf);
    }
    return maxflow;
}
void Find_Path(int n){
    for(int i=1;i<=n;i++){
        if(!tag[i]){
            int x=i,flag=0,to=0;
            while(x!=ep){
                for(int j=head[x];j!=-1;j=q[j].next){
                    if(q[j].val) continue;
                    to=q[j].to;
                    printf("%d ",x);
                    tag[x]=1,flag=1;
                    if(to==sp) break;
                    x=to-n;
                    break;
                }
                if(to==sp) break;
            }
            if(flag) puts("");
        }
    }
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    sp=0,ep=2*n+1;
    init();
    for(int i=0;i<m;i++){
        int from,to;
        scanf("%d%d",&from,&to);
        add_edge(from,to+n,1);
    }
    for(int i=1;i<=n;i++){
        add_edge(sp,i,1);
        add_edge(i+n,ep,1);
    }
    int res=n-dinic(ep);
    Find_Path(n);
    printf("%d\n",res);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值