题意:题目的意思很清晰,对于一个有向图,将N个点划分成最少的集合个数,同时满足俩个条件:
1) 任意俩点,若互相可达,则必须在同一个集合中
2)属于同一个集合的任意俩个点对(u,v),至少存在一条路径,使得v对于u 可达 或者 u 对于v 可达
分析:对于上述俩个条件,为了简化问题,需要进行缩点,属于同一个强连通分量的点,缩成同一个点,重新构图,可以用tarjan 算法;
这样,第一个条件就一定满足了,接着只剩下第二个条件了,其实,任意俩点,只要在同一条有向路径上,则可以属于一个集合,,,那么问题就转化为用最小的有向路径去覆盖所有的点(最小路径覆盖数==点数-最大匹配数)
View Code
#include<iostream> #include<algorithm> #include<vector> #include<stack> using namespace std; const int N = 5000+10; vector<int> g[N],g2[N]; stack<int> st; int n,dfn[N],low[N],f[N],index; int num,match[N]; bool vis[N],instack[N]; void tarjan(int u)//求强连通分支 { int v; dfn[u] = low[u] = index++; st.push(u); instack[u] = true; vis[u] = true; for(int i=0; i<g[u].size(); i++) { v = g[u][i]; if(!vis[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if(instack[v]) low[u] = min(low[u], dfn[v]); } if(dfn[u] == low[u]) { do { v = st.top(); instack[v] = false; st.pop(); f[v]=num;//记录每一个点所在的强连通分支 } while(v != u); num++; } } int path(int s) { vector<int>::iterator it=g2[s].begin(); for(;it!=g2[s].end();it++) { int v=*it; if(!vis[v]) { vis[v]=true; if(match[v]==-1 || path(match[v])) { match[v]=s; return 1; } } } return 0; } int main() { int T,cas=0,m,a,b; scanf("%d",&T); while(T--) { scanf("%d %d",&n,&m); for(int i=0;i<=n;i++) { g[i].clear(); g2[i].clear(); } while(m--) { scanf("%d %d",&a,&b); g[a].push_back(b); } memset(vis,false,sizeof(vis)); memset(instack,false,sizeof(instack)); index=num=0; for(int i=1;i<=n;i++) { if(!vis[i]) tarjan(i); } for(int i=1;i<=n;i++) //重新构图 for(int j=0;j<g[i].size();j++) { if(f[i]!=f[g[i][j]]) { g2[f[i]].push_back(f[g[i][j]]); } } memset(match,-1,sizeof(match)); int ans=0; for(int i=0;i<num;i++)//求最大匹配数 { memset(vis,false,sizeof(vis)); ans+=path(i); } printf("%d\n",num-ans); } return 0; }