洛谷P2764 最小路径覆盖问题

刚一看到这道题十分的懵,完全不知道从何下手

然后看了看题解后发现,这道题的关键就是计算可以合并多少次路径,然后最小的路径覆盖数就是总点数-合并次数。

举个例子:(仅仅是让你理解上面一句话,而不一定是真正的代码运行流程)

 

初始:

合并1次:

 

合并2次:

 

合并3次:

 

所以最后最小路径覆盖数即为:5-3=2

(注意:1、4号点之间不能合并,因为路径是链状的,这说明了很重要的一点:每一个点都最多只能合并其它点一次,被合并一次,这正是之后在建网络时要把源点到x的边和x'到汇点的边的容量赋值为1的原因)

 通过思考再看一看题解,我们发现:

可以先把每个点拆开,其中一个代表由这个点为起点(x),另一个代表由这个点为终点(x')。

然后在源点(s)与每一个x之间连一条容量为1的边,在每一个x'与汇点(t)之间连一条容量为1的边。

最后对于每一个输入的边(x,y),我们都在x与y'之间连一条容量为inf的边。

Tips:一定不要忘加反向边!一定不要忘加反向边!一定不要忘加反向边!(因为介个我调了好久QAQ)

那么如果有一条路径s-x-y'-t的流量为1,那就说明将x所代表的点与y'所代表的点合并。

所以在我们跑完一遍最大流之后,得到的最大流量就是合并次数。

最小的路径覆盖数(答案)就是总点数-合并次数。

可能文字有一点绕,所以大家可以看代码理解:

#include<stdio.h>
#include<string.h>
#include<iostream>
#define maxn 1010
#define maxm 100010
#define inf 0x3f3f3f3f
using namespace std;

int bg[maxn],nt[maxm],to[maxm],w[maxm],e=1;
int q[maxn<<2],dep[maxn],n,m,s,t,ans,nxt[maxn],pre[maxn];

void insert(int x,int y,int z) {
    nt[++e]=bg[x];
    to[e]=y;
    w[e]=z;
    bg[x]=e;
}

int bfs() {
    memset(dep,0,sizeof(dep));
    int i,f,l,u,v;
    f=l=1;
    q[f]=s;
    dep[s]=1;
    while (f<=l) {
        u=q[f++];
        for (i=bg[u];i;i=nt[i]) {
            v=to[i];
            if (!w[i] || dep[v]) continue;
            dep[v]=dep[u]+1;
            q[++l]=v;
        }
    }
    return dep[t];
}

int dfs(int x,int s) {
    int i,u,tmp,res=0;
    if (x==t) return s;
    if (!s) return 0;
    for (i=bg[x];i;i=nt[i]) {
        u=to[i];
        if (dep[u]==dep[x]+1) {
            tmp=dfs(u,min(s,w[i]));
            if (!tmp) continue;
            res+=tmp;
            s-=tmp;
            w[i]-=tmp;
            w[i^1]+=tmp;
            if (!s) break;
        }
    }
    return res;
}

int dinic() {
    int i,res=0;
    while (bfs()) {
        res+=dfs(s,inf);
    }	
    return res;
}

int main() {
    int i,j,x,y;
    scanf("%d%d",&n,&m);
    s=0; t=(n<<1)+1;
    for (i=1;i<=n;i++) {
        insert(s,i,1);
        insert(i,s,0);
        insert(i+n,t,1);
        insert(t,i+n,0);
    }
    for (i=1;i<=m;i++) {
        scanf("%d%d",&x,&y);
        insert(x,y+n,inf);
        insert(y+n,x,0);
    }
    ans=n-dinic();
    for (i=2;i<=e;i+=2) 
        if (n<to[i] && to[i]<t && s<to[i^1] && to[i^1]<=n && w[i^1])  {
            pre[to[i]-n]=to[i^1];
            nxt[to[i^1]]=to[i]-n;
        }
    for (i=1;i<=n;i++) {
        if (!pre[i]) {
            j=i;
            while (j) {
                printf("%d ",j);
                j=nxt[j];
            }
            printf("\n");	
        }
    }
    printf("%d\n",ans);
    return 0;
}

对了,讲一下我的方案输出方法:(利用双向链表维护)

在跑玩dinic后扫一下每一条(正向)边(构造的网络边),如果这条边连接着两个非源非汇的点(x,y)且它的反向边不为0(即这条边被流过),就使pre[y]=x; nxt[x]=y;

最后输出时就看当前点的前驱是否为0,若是,其就为一条路径的起始点,从它开始一直遍历其后继并输出,直到跑完为止

Tips:一定要注意细节,比如x'在变回原来的点时要-n

完结散花QwQ

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值