题目描述
一个有向图 称为半连通的 (Semi-Connected),如果满足:,满足 或 ,即对于图中任意两点 ,存在一条 到 的有向路径或者从 到 的有向路径。
若 满足 , 是 中所有跟 有关的边,则称 是 的一个导出子图。若 是 的导出子图,且 半连通,则称 为 的半连通子图。若 是 所有半连通子图中包含节点数最多的,则称 是 的最大半连通子图。
给定一个有向图 ,请求出 的最大半连通子图拥有的节点数 ,以及不同的最大半连通子图的数目 。由于 可能比较大,仅要求输出 对 的余数。
输入格式
第一行包含两个整数 。分别表示图 的点数与边数, 的意义如上文所述。
接下来 行,每行两个正整数 ,表示一条有向边 。图中的每个点将编号为 ,保证输入中同一个不会出现两次。
输出格式
应包含两行,第一行包含一个整数 ,第二行包含整数 。
样例 #1
样例输入 #1
6 6 20070603
1 2
2 1
1 3
2 4
5 6
6 4
样例输出 #1
3
3
提示
对于 的数据,,,。
思路
显然, 强连通分量一定是半连通的, 我们可以先把强连通分量缩点, 转化为DAG图, 然后再考虑。
如图, 很显然半连通分量就是一条直链, 不能有分叉, 因为分叉后的点没法到达另一个分叉的点(所有的双向边都缩为了一点)。
故只需要找到点数最多的链, 且求出有多少个这样的链就行。
拓扑图有个好处就是可以递推地用动态规划处理:
状态表示:
f[i]:
以该点为结尾的链中节点数量
g[i]:
当前节点数量的链有几条
状态属性:
f[i]:
max
g[i]:
数量
状态计算:
当 f[j] < f[i] + size[j]
时
f[j] = f[i] + size[j]
g[j] = g[i] 更新一下数量
否则当f[j] == f[i] + size[j]
时, 说明是遇到了另一条相同点数的链, 这样就加一下数量
g[j] = g[i] + g[j]
最后再遍历一遍f[i]
, 求出 maxf 最大点数量的半连通子图, 和 数量sum。
代码
const int N = 1e5 + 10, M = 2e6 + 10;
int n, m, mod;
// tarjan
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], size[N], scc_cnt;
// 最大连通图点数量, 最大连通图数量
int f[N], g[N];
// 存图
int h[N], hs[N], e[M], ne[M], idx;
void add(int h[], int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++timestamp;
stk[++top] = u, in_stk[u] = true;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (!dfn[j])
{
tarjan(j);
low[u] = min(low[u], low[j]);
}
else if (in_stk[j])
low[u] = min(low[u], dfn[j]);
}
if (low[u] == dfn[u])
{
++scc_cnt;
int y;
do
{
y = stk[top--];
in_stk[y] = false;
id[y] = scc_cnt;
size[scc_cnt]++;
} while (y != u);
}
}
int main()
{
FasterIO;
mem(h, -1);
mem(hs, -1);
cin >> n >> m >> mod;
while (m--)
{
int a, b;
cin >> a >> b;
add(h, a, b);
}
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
unordered_set<LL> S;
for (int i = 1; i <= n; i++)
for (int j = h[i]; ~j; j = ne[j])
{
int k = e[j];
int a = id[i], b = id[k];
LL hash = a * 100000ll + b; // hash来去掉重边, 这里a <= 1e5, 乘上1e5然后再加上b 就不会冲突
if (a != b && !S.count(hash))
{
add(hs, a, b);// 缩点建图
S.insert(hash);
}
}
for (int i = scc_cnt; i >= 1; i--)
{
if (!f[i])
{
f[i] = size[i];
g[i] = 1;
}
for (int j = hs[i]; ~j; j = ne[j])
{
int k = e[j];
if (f[k] < f[i] + size[k])
{
f[k] = size[k] + f[i];
g[k] = g[i];
}
else if (f[k] == f[i] + size[k])
g[k] = (g[k] + g[i]) % mod;
}
}
int maxf = 0, sum = 0;
for (int i = 1; i <= scc_cnt; i++)
{
if (f[i] > maxf)
{
maxf = f[i];
sum = g[i];
}
else if (f[i] == maxf)
sum = (sum + g[i]) % mod;
}
cout << maxf << "\n"
<< sum << "\n";
return 0;
}