目录
显示
题目链接
https://www.luogu.com.cn/problem/P2764
题解
一个非常经典的模型。
将一个点 \(x\) 拆分成两个点 \(x_1,x_2\)。如果原图中存在 \(x \to y\),就在新图上连 \(x_1 \to y_2\)。
则最小路径覆盖 \(=n−\) 二分图最大匹配数。
题外话,这里的最小路径覆盖要求一个点最多覆盖一次,假如允许一个点可以覆盖多次的话该怎么做呢?
我们只需对原图作传递闭包(从而找到一个点可以到达的其他所有点),再按照上面的方法做就行了。
#include <cstdio> #include <cstring> #include <algorithm> #include <queue> #define INF 0x3f3f3f3f using namespace std; struct edge { int v,w,next; }e[100005]; int s,t,cnt=1; int head[1005],dep[1005],vis[1005],cur[1005],nxt[1005]; void addedge(int u,int v,int w) { e[++cnt].v=v; e[cnt].w=w; e[cnt].next=head[u]; head[u]=cnt; } bool bfs() { queue<int> q; memset(dep,INF,sizeof(dep)); memset(vis,0,sizeof(vis)); memcpy(cur,head,sizeof(head)); dep[s]=0; vis[s]=1; q.push(s); while(!q.empty()) { int p=q.front(); q.pop(); vis[p]=0; for(int i=head[p];i;i=e[i].next) if(dep[e[i].v]>dep[p]+1&&e[i].w) { dep[e[i].v]=dep[p]+1; if(!vis[e[i].v]) { vis[e[i].v]=1; q.push(e[i].v); } } } if(dep[t]==INF)return 0; return 1; } int dfs(int p,int w) { if(p==t)return w; int used=0; for(int i=cur[p];i;i=e[i].next) { cur[p]=i; if(dep[e[i].v]==dep[p]+1&&e[i].w) { int flow=dfs(e[i].v,min(w-used,e[i].w)); if(flow) { used+=flow; e[i].w-=flow; e[i^1].w+=flow; if(used==w)break; } } } return used; } int main() { int n,m,ans=0; scanf("%d%d",&n,&m); s=2*n+1,t=2*n+2; for(int i=1;i<=n;i++) { addedge(s,i,1); addedge(i,s,0); addedge(i+n,t,1); addedge(t,i+n,0); } for(int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); addedge(u,v+n,1); addedge(v+n,u,0); } while(bfs()) ans+=dfs(s,INF); for(int u=1;u<=n;u++) for(int i=head[u];i;i=e[i].next) if(e[i].v!=s&&!e[i].w)nxt[u]=e[i].v-n; for(int i=1;i<=n;i++) { bool flag=false; for(int j=i;j&&!vis[j];j=nxt[j]) { printf("%d ",j); vis[j]=1,flag=true; } if(flag)puts(""); } printf("%d\n",n-ans); return 0; }