求最小点路径覆盖并打印
有向无环图的最小路径覆盖问题包括两种(设G是一个有向无环图,S是G的一个路径集合):
(1)最小点路径覆盖:满足对于G中所有的点i,i在S中的一条路径中出现,且只在S中的一条路径中出现,求S的最小容量;
(2)最小边路径覆盖:满足对于G中所有的边E,E在S中的一条路径中出现,且只在S中的一条路径中出现,求S的最小容量;
(1)最小点路径覆盖:
建立一个新图,将G中的每个点i在新图中拆成两个点i'、i'',若G中存在边<i, j>则在新图中连边<i', j''>,显然新图是一个二分图,求其最大匹配,则(N-新图最大匹配的值)就是最小点路径覆盖值。
时间复杂度:O(NM)(Hungary算法)
(2)最小边路径覆盖:
对于图中的每个点i,设D[i]为(i的入度-i的出度)的值,按照D[i]将图中的点分类:D[i]<0的称为“入少出多”的点,D[i]>0的称为“出少入多”的点,D[i]=0的称为“入出相等”的点。则有:
定理 有向无环图中最小边路径覆盖的值等于图中所有“入少出多”的点的D值之和。
证明:
其实只需证明: 对于一个至少有一条边的有向无环图,必然存在一条路径,其起点是“入少出多”的点,终点是“出少入多”的点,所有中间点都是“入出相等”的点(只要不断的在图中找到并删去这条路径,直到图中无边为止)。
首先,由于图中无环,一定存在“入少出多”的点和“出少入多”的点。
然后,假设图中所有“入少出多”的点的后继(注意:后继和后代是不同的,一个点的后代包括这个点的所有后继、所有后继的后继、所有后继的后继的后继……)也都是“入少出多”的点,则图中所有“入少出多”的点构成了一个闭合子图,在这个闭合子图中,由于所有的点都是“入少出多”的,整个子图的入度之和必然大于出度之和,这是不可能的(有向图中的所有点入度之和必然等于所有点出度之和),故可得: 图中必然存在至少一个“入少出多”的点,它有不是“入少出多”的后继。
这样,在这些拥有不是“入少出多”的后继的点中选择一个点i,将其作为路径的起点,在它的不是“入少出多”的后继中选出一个点j,若j是“出少入多”的点,则边<i, j>就是符合条件的一条路径,结束;若这样的j都是“入出相等”的点,则考虑j的后代:j的后继可能都是“入少出多”的,但j的后代中必然存在至少一个点j'不是“入少出多”的(否则j的所有后代也将构成全都是“入少出多”的闭合子图),这些j'中必然存在一个点的前趋i'是“入少出多”的,这是,需要舍弃原来的路径,将i'作为新路径的起点,j'作为新路径的第一个中间点,继续找;若j的后继不全是“入少出多”的,则若其中有“出少入多”的则路径已找到,若都是“入出相等”的,由于图中无环,将路径不断延伸,最终一定会找到合法路径。
由此可得,对于任何有向无环图,这样的路径都是存在的,也就证明了一开始的求最小边路径覆盖值的定理。
求有向无环图最小边路径覆盖值的时间复杂度:O(M+N)。
(1)最小点路径覆盖:满足对于G中所有的点i,i在S中的一条路径中出现,且只在S中的一条路径中出现,求S的最小容量;
(2)最小边路径覆盖:满足对于G中所有的边E,E在S中的一条路径中出现,且只在S中的一条路径中出现,求S的最小容量;
(1)最小点路径覆盖:
建立一个新图,将G中的每个点i在新图中拆成两个点i'、i'',若G中存在边<i, j>则在新图中连边<i', j''>,显然新图是一个二分图,求其最大匹配,则(N-新图最大匹配的值)就是最小点路径覆盖值。
时间复杂度:O(NM)(Hungary算法)
(2)最小边路径覆盖:
对于图中的每个点i,设D[i]为(i的入度-i的出度)的值,按照D[i]将图中的点分类:D[i]<0的称为“入少出多”的点,D[i]>0的称为“出少入多”的点,D[i]=0的称为“入出相等”的点。则有:
定理 有向无环图中最小边路径覆盖的值等于图中所有“入少出多”的点的D值之和。
证明:
其实只需证明: 对于一个至少有一条边的有向无环图,必然存在一条路径,其起点是“入少出多”的点,终点是“出少入多”的点,所有中间点都是“入出相等”的点(只要不断的在图中找到并删去这条路径,直到图中无边为止)。
首先,由于图中无环,一定存在“入少出多”的点和“出少入多”的点。
然后,假设图中所有“入少出多”的点的后继(注意:后继和后代是不同的,一个点的后代包括这个点的所有后继、所有后继的后继、所有后继的后继的后继……)也都是“入少出多”的点,则图中所有“入少出多”的点构成了一个闭合子图,在这个闭合子图中,由于所有的点都是“入少出多”的,整个子图的入度之和必然大于出度之和,这是不可能的(有向图中的所有点入度之和必然等于所有点出度之和),故可得: 图中必然存在至少一个“入少出多”的点,它有不是“入少出多”的后继。
这样,在这些拥有不是“入少出多”的后继的点中选择一个点i,将其作为路径的起点,在它的不是“入少出多”的后继中选出一个点j,若j是“出少入多”的点,则边<i, j>就是符合条件的一条路径,结束;若这样的j都是“入出相等”的点,则考虑j的后代:j的后继可能都是“入少出多”的,但j的后代中必然存在至少一个点j'不是“入少出多”的(否则j的所有后代也将构成全都是“入少出多”的闭合子图),这些j'中必然存在一个点的前趋i'是“入少出多”的,这是,需要舍弃原来的路径,将i'作为新路径的起点,j'作为新路径的第一个中间点,继续找;若j的后继不全是“入少出多”的,则若其中有“出少入多”的则路径已找到,若都是“入出相等”的,由于图中无环,将路径不断延伸,最终一定会找到合法路径。
由此可得,对于任何有向无环图,这样的路径都是存在的,也就证明了一开始的求最小边路径覆盖值的定理。
求有向无环图最小边路径覆盖值的时间复杂度:O(M+N)。
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=450;
const int inf=1<<25;
const int s=0;
int n,m;
struct edge{
int v,next,w;
}edge[maxn*maxn];
int head[maxn],cnt;//for sap
void addedge(int u, int v, int w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].v=u;
edge[cnt].w=0;
edge[cnt].next=head[v];
head[v]=cnt++;
}
int sap(int t)
{
int pre[maxn],cur[maxn];
int dis[maxn],gap[maxn];
int flow=0 , aug=inf ,u;
bool flag;
for (int i=0 ; i<=t ; ++i)
{
cur[i]=head[i];
gap[i]=dis[i]=0;
}
gap[s]=t+1;
u=pre[s]=s;
while (dis[s]<=t)
{
flag=0 ;
for (int &j=cur[u] ; ~j ; j=edge[j].next)
{
int v=edge[j].v;
if (edge[j].w>0 && dis[u]==dis[v]+1)
{
flag=1;
if(edge[j].w<aug)aug=edge[j].w;
pre[v]=u;
u=v;
if (u==t)
{
flow+=aug;
while (u!=s)
{
u=pre[u];
edge[cur[u]].w-=aug;
edge[cur[u]^1].w+=aug;
}
aug=inf;
}
break;
}
}
if (flag)continue ;
int mindis=t+1;
for (int j=head[u]; ~j ; j=edge[j].next)
{
int v=edge[j].v;
if (edge[j].w>0 && dis[v]<mindis)
{
mindis=dis[v];
cur[u]=j;
}
}
if(--gap[dis[u]]==0)break;
gap[dis[u]=mindis+1]++;
u=pre[u];
}
return flow;
}
/////////////
bool vis[maxn];
int path[maxn],pcnt;
//保存路径并记录路径长度
void dfs (int u)
{
vis [u]=true;
path[pcnt++]=u;
for(int p=head[u] ; ~p ; p=edge[p].next)
if(!vis[edge[p].v])
{
if(edge[p].v)
{
if(edge[p].w==0)dfs(edge[p].v-n);
}
}
}
void build_graph()
{
for (int i=1 ; i<=n ; ++i)
{
addedge (0 , i , 1);
addedge (i+n , 2*n+1 , 1);
}
}
void init ()
{
memset (head , -1 , sizeof(head));
cnt=0;
}
int main ()
{
int u,v;
freopen ("path5.in" , "r" , stdin);
freopen ("out1.txt" , "w" , stdout);
while (~scanf("%d%d",&n,&m))
{
init();
for (int i=0 ; i<m ; ++i)
{
scanf("%d%d",&u,&v);
addedge(u , v+n , 1);
}
build_graph();
int ans=sap(2*n+1);
memset (vis , false , sizeof(vis));
for (int i=1 ; i<=n ; ++i)
{
pcnt=0;
if(vis[i])continue;
dfs(i);
for (int j=0 ; j<pcnt ; ++j)
printf("%d%c",path[j],j==pcnt-1?'\n':' ');
}
printf("%d\n",n-ans);
}
return 0;
}
JOJ 2730 stock(torry唐牛出品)
给出n个k长的序列,问最少需要多少个图画出所有的序列,使每个图的序列不相交。
一开构图时根据2点之间不相交的关系连无向边,结果构出了一个有环无向图(囧)。
正确构图方法是将无向定义有向,并去环, 这样可以考虑使等价关系变成偏序关系,即互不相交变成当一个序列完全大于另一个序列时连一条有向边,这样就是标准的DAG图,再求下最小路径覆盖就好了。
#include <cstdio>
#include <cstring>
const int maxn=210;
const int inf=1<<25;
const int s=0;
int n,m;
struct edge{
int v,next,w;
}edge[maxn*maxn];
int head[maxn],cnt;//for sap
void addedge(int u, int v, int w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt++;
edge[cnt].v=u;
edge[cnt].w=0;
edge[cnt].next=head[v];
head[v]=cnt++;
}
int sap(int t)
{
int pre[maxn],cur[maxn];
int dis[maxn],gap[maxn];
int flow=0 , aug=inf ,u;
bool flag;
for (int i=0 ; i<=t ; ++i)
{
cur[i]=head[i];
gap[i]=dis[i]=0;
}
gap[s]=t+1;
u=pre[s]=s;
while (dis[s]<=t)
{
flag=0 ;
for (int &j=cur[u] ; ~j ; j=edge[j].next)
{
int v=edge[j].v;
if (edge[j].w>0 && dis[u]==dis[v]+1)
{
flag=1;
if(edge[j].w<aug)aug=edge[j].w;
pre[v]=u;
u=v;
if (u==t)
{
flow+=aug;
while (u!=s)
{
u=pre[u];
edge[cur[u]].w-=aug;
edge[cur[u]^1].w+=aug;
}
aug=inf;
}
break;
}
}
if (flag)continue ;
int mindis=t+1;
for (int j=head[u]; ~j ; j=edge[j].next)
{
int v=edge[j].v;
if (edge[j].w>0 && dis[v]<mindis)
{
mindis=dis[v];
cur[u]=j;
}
}
if(--gap[dis[u]]==0)break;
gap[dis[u]=mindis+1]++;
u=pre[u];
}
return flow;
}
int stock[maxn][30];
int k;
bool valid (int x, int y)
{
for (int i=0 ; i<k ; ++i)
{
if(stock[x][i]<=stock[y][i])return false ;
}
return true ;
}
void build_graph()//o(n*n*k)
{
memset (head , -1 , sizeof(head));
cnt=0;
for (int i=0 ; i<n ; ++i)
{
addedge(0 , i+1 , 1);
addedge(i+1+n , 2*n+1 , 1);
for (int j=0 ; j<n ; ++j)
{
if(valid(i,j))
addedge(i+1 , j+1+n , 1);
}
}
}
int main ()
{
int u,v;
int cas;
scanf("%d",&cas);
for (int I=1 ; I<=cas ; ++I)
{
scanf("%d%d",&n,&k);
for (int i=0 ; i<n ; ++i)
{
for (int j=0 ; j<k ; ++j)
{
scanf("%d",*(stock+i)+j);
}
}
build_graph();
printf("Case #%d: %d\n",I,n-sap(2*n+1));
}
return 0;
}
本文深入探讨了有向无环图中最小点路径覆盖与最小边路径覆盖问题,详细介绍了两种路径覆盖问题的解决方法及其实现步骤。通过构建新图和应用匈牙利算法解决最小点路径覆盖问题,以及利用Dijkstra分类法和最大匹配原则求解最小边路径覆盖问题,文章提供了一套完整的解决方案。
945

被折叠的 条评论
为什么被折叠?



