问题:给你若干个点和边,问最少用几条路可以覆盖掉全图。
问题转化:网络流
题目传送门
分析:
我们首先将原图用n条路径覆盖,每条边只经过每个节点。
现在尽量合并更多的路径(即将两个路径通过一条边首尾相连)。
可以知道,每合并两条路径,图中的路径覆盖数就会减少1。
所以我们只需要利用网络流合并相关的路径即可。
答案求解:
首先将每个节点拆成(Xi,Yi)两个节点,建立源点和汇点,分别连接(S,Xi)和(Yi,T)。
然后对于每一条原图中的边,建立边(Xi,Yi)即可。
这样每一条增广路都只会经过2个节点(Xa,Yb),对应合并的两个节点。
由于每个节点至多与一个节点合并,故边(S,Xi)和(Yi,T)容量为1。
此时的最大流对应的就是最多可以合并的路径数。
至于输出路径嘛,这题是SPJ,瞎搞就行。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <cmath>
#define il inline
using namespace std;
const int inf=0x7fffffff;
const int maxm=1e5+1;
const int anx=150;
int head[maxm],to[maxm*2],cap[maxm*2],net[maxm*2];
int top[maxm];
int cnt=1;
il void add(int x,int y,int c){cnt++,to[cnt]=y,cap[cnt]=c,net[cnt]=head[x],head[x]=cnt;}
int flow[maxm],pre[1100],id[maxm],preflow=0,anaa;
int ans[maxm];
queue <int> dl;
bool vis[maxm];
il int BFS(int s,int t)
{
while(!dl.empty()) dl.pop();
memset(pre,-1,sizeof(pre));
pre[s]=0,flow[s]=inf;
dl.push(s);
while(!dl.empty())
{
int x=dl.front();
dl.pop();
if(x==t) break;
for(int i=head[x];i;i=net[i])
if(to[i]!=s&&cap[i]>0&&pre[to[i]]==-1)
{
pre[to[i]]=x,id[to[i]]=i;
flow[to[i]]=min(flow[x],cap[i]);
dl.push(to[i]);
}
}
if(pre[t]==-1) return -1;
else return flow[t];
}
il void change_cap(int s,int t,int x)
{
int now=t,last,sum=0;
while(now!=s)
{
cap[id[now]]-=x;
cap[id[now]^1]+=x;
now=pre[now];
}
}
il int maxflow(int s,int t)
{
int max_flow=0;
int d=0;
while((d=(BFS(s,t)))!=-1)
{
change_cap(s,t,d);
max_flow+=d;
}
return max_flow;
}
inline void adx(int x,int y,int cax)
{
add(x,y,cax),add(y,x,0);
}
int main()
{
int n,m,s=0,t;
scanf("%d%d",&n,&m);
t=1000;
for(int i=1;i<=n;i++) adx(s,i,1),adx(i+anx,t,1);
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
adx(a,b+anx,1);
}
int Ans=maxflow(s,t);
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=net[j])
if(cap[j]==0) ans[i]=to[j]-anx;
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
int now=i;
while(now!=-anx)
printf("%d ",now),vis[now]=1,now=ans[now];
puts("");
}
return printf("%d\n",n-Ans)*0;
}