链接
相关知识
- 有向图中,路径覆盖就是在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每个顶点一次且仅一次);如果不考虑图中存在回路,那么每条路径就是一个弱连通子集.
- 对于一个路径覆盖,有如下性质:
- 每个顶点属于且只属于一个路径。
- 路径上除终点外,从每个顶点出发只有一条边指向路径上的另一顶点。
- 路径覆盖与二分图匹配的关系(必须是有向无环图):
最小路径覆盖=|P|-最大匹配数
其中最大匹配数的求法是把P中的每个顶点pi分成两个顶点pi’与pi”,如果在p中存在一条pi到pj的边,那么在二分图P’中就有一条连接pi’与pj”的无向边;这里pi’ 就是p中pi的出边,pj”就是p中pj 的一条入边;
对于公式:最小路径覆盖=|P|-最大匹配数;可以这么来理解;
如果匹配数为零,那么P中不存在有向边,于是显然有:
最小路径覆盖=|P|-最大匹配数=|P|-0=|P|;即P的最小路径覆盖数为|P|;
P’中不在于匹配边时,路径覆盖数为|P|;
如果在P’中增加一条匹配边pi’-->pj”,那么在图P的路径覆盖中就存在一条由pi连接pj的边,也就是说pi与pj 在一条路径上,于是路径覆盖数就可以减少一个;
如此继续增加匹配边,每增加一条,路径覆盖数就减少一条;直到匹配边不能继续增加时,路径覆盖数也不能再减少了,此时就有了前面的公式;但是这里只 是说明了每条匹配边对应于路径覆盖中的一条路径上的一条连接两个点之间的有向边;下面来说明一个路径覆盖中的每条连接两个顶点之间的有向边对应于一条匹配 边;
与前面类似,对于路径覆盖中的每条连接两个顶点之间的每条有向边pi—>pj,我们可以在匹配图中对应做一条连接pi’与pj”的边, 显然这样做出来图的是一个匹配图(这一点用反证法很容易证明,如果得到的图不是一个匹配图,那么这个图中必定存在这样两条边 pi’—pj” 及 pi’ —-pk”,(j!=k),那么在路径覆盖图中就存在了两条边pi–>pj, pi—>pk ,那边从pi出发的路径就不止一条了,这与路径覆盖图是矛盾的;还有另外一种情况就是存在pi’—pj”,pk’—pj”,这种情况也类似可证);
题意
把有向图中,划分为几个区域,区域中u可以到v,或者v到u,但是路径中没有其他区域的点。经过划分,求划分区域最少的数目。
解析
每个强连通分量中,任意两个点,可以到达。所以可以将每个强连通分量缩点,之后就可以转化为边覆盖问题,为了最少,求最小边覆盖就可以了。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <map>
#include <vector>
#include <stack>
#include <cmath>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn = 10000+100;
typedef long long LL;
int dfn[maxn], low[maxn];
stack<int>s;
int scc_cnt;
int sccno[maxn];
int dfs_clock;
vector<int>g[maxn];
vector<int>G[maxn];
int match[maxn];
int vis[maxn];
void init() {
scc_cnt = 0;
memset(match, -1, sizeof(match));
dfs_clock = 0;
while (!s.empty())
s.pop();
for (int i=0; i<maxn; i++)
g[i].clear(), dfn[i] = 0, low[i] = 0, sccno[i]=0;
}
void tarjan(int u) {
dfn[u] = low[u] = ++dfs_clock;
s.push(u);
for (int i=0; i<g[u].size(); i++) {
int v = g[u][i];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if (!sccno[v])
low[u] = min(dfn[v], low[u]);
}
if (dfn[u] == low[u]) {
scc_cnt++;
for (;;) {
int x = s.top();
s.pop();
sccno[x] = scc_cnt;
if (x == u)
break;
}
}
}
int findpath(int u) {
for (int i=0; i<G[u].size(); i++) {
int v = G[u][i];
if (!vis[v]) {
vis[v] = 1;
if (match[v] == -1 || findpath(match[v])) {
match[v] = u;
return 1;
}
}
}
return 0;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
int n, m;
scanf("%d%d", &n, &m);
init();
for (int i=0; i<m; i++) {
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
for (int i=1; i<=n; i++) {
if (!dfn[i])
tarjan(i);
}
for (int i=1; i<=scc_cnt; i++)
G[i].clear();
for (int i=1; i<=n; i++)
{
for (int j=0; j<g[i].size(); j++) { //建立二分图
if (sccno[i] != sccno[g[i][j]]) {
int u = sccno[i];
int v = sccno[g[i][j]]+scc_cnt;
G[u].push_back(v);
}
}
}
int res = 0;
for (int i=1; i<=scc_cnt; i++) { //求最大匹配
if (match[i] == -1) {
memset(vis, 0, sizeof(vis));
res += findpath(i);
}
}
printf("%d\n", scc_cnt-res);
}
return 0;
}