刚一看到这道题十分的懵,完全不知道从何下手
然后看了看题解后发现,这道题的关键就是计算可以合并多少次路径,然后最小的路径覆盖数就是总点数-合并次数。
举个例子:(仅仅是让你理解上面一句话,而不一定是真正的代码运行流程)
初始:
合并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