最小路径覆盖【题解】

前言

搞了几天网络流,总算会做题了

题面

sol

显然,如果是个dp,那么转移就是到某一个点的状态转移给下一个点。
但是下一个点不能被选,也就是一个点被选的状态加上与其有连边的点没有选的状态转移给这一个点被选的状态。
然后发现,这个dp显然很难设出状态与变量意义,但换成网络流很好设。
但是网络流只能求最大流,没有最小。这道题是最小路径覆盖,所以要把最小转换成最大。我们发现,在最小路径覆盖下经过的边是最多的,经过的边是点数减去路径数。所以求最大路径数。
然后约束条件就是一个点和一个边只能经过一次。但是我们发现一个点只能经过一次的约束更强,如果两个点之间有一条边,两点只经过一次,那么他们之间的边经过少于等于一次。
我们发现流过一条边就要算一次贡献,相当于就要往汇点送去一的流量,所以经过一个边就要往汇点送去一的流量。
显然不是每一个边都可以送去流量,如果这边的终点已经经过了,那么就不能送去流量,也就是该终点若经过一次,该边的容量就已经为零,不能再有流量走了,也就不会有往汇点的贡献。
那么这样看来,所有终点相同的边实际上是一条边。我们把这条边叫做上述终点的A边。在网络流构图中,经过这条边就可以去汇点送贡献,这条边的终点是汇点。
由于每一个点都可以作为起点,那么每一个点都是与源点相连。然后每一个点连出一条这个点能去往的一个点的A边的起点的边。

code

#include<bits/stdc++.h>
using namespace std;
template <class T>
inline void read(T&data){
    data=0;
    register char ch=0;
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch<='9'&&ch>='0'){
        data=(data<<3)+(data<<1)+(ch&15);
        ch=getchar();
    }
    return;
}
const int _ =310,__ = 13001;
const int INF = 2e9;
int n,m,S,T;
int to[__<<1],head[_],cur[_],w[__<<1],nxt[__<<1],deep[_],cnt=-1;
inline void add(register int a,register int b,register int c){
    to[++cnt]=b,nxt[cnt]=head[a],w[cnt]=1,head[a]=cnt;
    to[++cnt]=a,nxt[cnt]=head[b],w[cnt]=0,head[b]=cnt;
}
inline bool bfs(){
    memset(deep,0,sizeof(deep));
    deep[S]=1;
    queue<int >Q;
    Q.push(S);deep[S]=1;
    while(!Q.empty()){
        register int now=Q.front();Q.pop();
        for(register int i=head[now];~i;i=nxt[i])
        {
            if(deep[to[i]])continue;
            if(w[i]<=0)continue;
            deep[to[i]]=deep[now]+1;
            Q.push(to[i]);
        }
    }
    return deep[T]!=0;
}
int dfs(register int now,register int flow){
    if(now==T)return flow;
    for(register int &i=cur[now];~i;i=nxt[i]){
        if(w[i]<=0)continue;
        if(deep[to[i]]!=deep[now]+1)continue;
        register int di=dfs(to[i],min(flow,w[i]));
        if(di>0){
            w[i]-=di;w[i^1]+=di;
            return di;
        }
    }
    return 0;
}
inline int dinic(){
    register int ret=0;
    while(bfs()){
        for(register int i=1;i<=T;++i)cur[i]=head[i];
        while(int d=dfs(S,INF))ret+=d;
    }
    return ret;
}
int stk[_];
bool pd[_];
int main(){
    read(n),read(m);
    memset(head,-1,sizeof(head));
    S=2*n+1,T=S+1;
    for(register int i=1;i<=n;++i)add(S,i,1);
    for(register int i=1;i<=n;++i)add(i+n,T,1);
    for(register int i=1;i<=m;++i){
        register int a,b;read(a),read(b);
        add(a,b+n,1);
    }
    register int ans = dinic();
    int top=0;
    for(register int i=1;i<=n;++i){//随便枚举一个点,然后先往上走,再往下走
        if(pd[i]==1)continue;
        top=0;
        register int now = i;pd[now]=1;
        while(1){
            for(register int j=head[now+n];~j;j=nxt[j])
                if(w[j^1]==0&&to[j]!=T){
                    stk[++top]=to[j];
                    now = to[j];
                    break;
                }
            if(pd[now]==1)break;
            pd[now]=1;
        }
        while(top){
            cout<<stk[top]<<' ';top--;
        }
        cout<<i<<' ';now=i;
        while(1){
            for(register int j=head[now];~j;j=nxt[j]){
                if(w[j]==0&&to[j]!=S&&to[j]!=T){
                    now=to[j]-n;break;
                }
            }
            if(pd[now]==1)break;
            pd[now]=1;cout<<now<<' ';
        }
        cout<<endl;
    }
    cout<<n-ans<<endl;

}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值