Now the king asks for your help, he wants to know the least number of states he have to divide the kingdom into.
The first line for each case contains two integers n, m(0 < n <= 5000,0 <= m <= 100000), the number of cities and roads in the kingdom. The next m lines each contains two integers u and v (1 <= u, v <= n), indicating that there is a road going from city u to city v.
1 3 2 1 2 1 3
2
题意为:给出n个点,编号1~n,给出一些有向边,构成有向图。要求把这个图最少划分为几个区域,满足下面3个条件
1) 如果对于两个点u,v可以互相到达,即存在两条有向边,那么u,v必须划分在一个区域里面
2) 1个点只能划分在一个区域里面
3) 一个区域里面的点,任意两个点u,v之间,至少有1方可以到达令1方,是通过路径到达,不一定是直接相连
定义:
最小路径覆盖:在图中找一些路径(路径数最少),使之覆盖了图中所有的顶点,且每个顶点有且仅和一条路径有关联。
最小顶点覆盖:在图中找一些点(顶点数最少),使之覆盖了图中所有的边,每条边至少和一个顶点有关联。
二分图:最小顶点覆盖=最大匹配数。
最小路径覆盖=顶点数-最大匹配数。
有上述条件1 可以得知首先得进行缩点,即首先找出所有的强联通分量,tarjan算法
条件2和3 就是让求最小路径覆盖.要求最小路径覆盖,就得求顶点数和最大匹配数,顶点数是缩点以后的顶点数,最大匹配数用匈牙利算法求出.(匈牙利
匹配算法讲解
为什么条件2与3就是求最小路径覆盖呢?首先看概念
最小路径覆盖:在图中找一些路径(路径数最少),使之覆盖了图中所有的顶点,且每个顶点有且仅和一条路径有关联。
注意是路径,不是边,这一条路径可以覆盖多个点,也就相当于把这些点划分到一个区域里面去了,同时也满足第3个条件。覆盖图中所有点,也就是把所有的顶点全部划分.每个顶点有且仅和一条路径有关联,也就是第二个条件: 1个点只能划分在一个区域里面.
做这道题就是弄清思路以后,就是套用模板了.
首先是建图,然后tarjan缩点,然后转化为DAG(有向无环图),在新的图上,利用匈牙利算法求出最大匹配,然后用公式最小路径覆盖=顶点数-最大匹配数。
就可以了。重要的是两个图之间的衔接,不要搞糊涂了.还有tarjan和匈牙利算法还是用邻接表(手写或者使用vector)吧,直接便利n个点常常会超时,邻接表
最保险.
缩点转化为DAG以后会有重边,不过不影响最大匹配. 比如1 2在一个强联通分量中,分量编号为1,3单独在一个强联通分量重,编号为2,在原来的图中
有边1->3 2->3 ,那么转化Dag会有两个边 1->2 1->2
#include <string.h>
#include <stdio.h>
using namespace std;
/*Tarjan算法求强连通缩点法*/
struct Edge//Tarjan算法缩点
{
int to,next;
}edge[100010];
int head[5010],tot;
int DFN[5010],num[5010],Low[5010],Stack[5010],Belong[5010],index,top,scc;
bool Instack[5010];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void Tarjan(int u)
{
int v;
Low[u]=DFN[u]=++index;
Stack[top++]=u;
Instack[u]=true;
for(int i=head[u];i!=-1;i=edge[i].next)
{
v=edge[i].to;
if(!DFN[v])
{
Tarjan(v);
if(Low[u]>Low[v])
Low[u]=Low[v];
}
else if(Instack[v]&&Low[u]>DFN[v])
Low[u]=DFN[v];
}
if(Low[u]==DFN[u])
{
scc++;
do
{
v=Stack[--top];
Instack[v]=false;
Belong[v]=scc;
num[scc]++;
}while(u!=v);
}
}
void solve(int n)
{
memset(DFN,0,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(num,0,sizeof(num));
index=top=scc=0;
for(int i=1;i<=n;i++)
if(!DFN[i])
Tarjan(i);
}
/*二分图匹配,邻接表匈牙利算法*/
int head2[5010],tot2;
Edge edge2[100010];
int linker[5010],un;
bool used[5010];
void init2()
{
tot2=0;
memset(head2,-1,sizeof(head2));
}
void addedge2(int u,int v)
{
edge2[tot2].to=v;
edge2[tot2].next=head2[u];
head2[u]=tot2++;
}
bool dfs(int u)
{
for(int i=head2[u];i!=-1;i=edge2[i].next)
{
int v=edge2[i].to;
if(!used[v])
{
used[v]=true;
if(linker[v]==-1||dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
}
return false;
}
int hungary()
{
int res=0;
memset(linker,-1,sizeof(linker));
for(int u=1;u<=un;u++)
{
memset(used,false,sizeof(used));
if(dfs(u))
res++;
}
return res;
}
int main()
{
int T;
cin>>T;
while(T--)
{
int n,m;
cin>>n>>m;
init();
while(m--)
{
int from,to;
scanf("%d%d",&from,&to);
addedge(from,to);
}
solve(n);
init2();
for(int i=1;i<=n;i++) //构造DAG
{
for(int j=head[i];j!=-1;j=edge[j].next)
{
int v=edge[j].to;
if(Belong[i]==Belong[v])
continue;
addedge2(Belong[i],Belong[v]);
}
}
un=scc;
int ans=hungary();
cout<<scc-ans<<endl;
}
return 0;
}